diff --git a/.docker/admin-server/Dockerfile b/.docker/admin-server/Dockerfile
index 45c78c0b..bbcf1429 100644
--- a/.docker/admin-server/Dockerfile
+++ b/.docker/admin-server/Dockerfile
@@ -1,18 +1,25 @@
-FROM openjdk:21-jdk as builder
+FROM openjdk:21-jdk AS builder
RUN microdnf install findutils
WORKDIR /app
-
COPY . /app
-
RUN chmod +x ./gradlew
-RUN ./gradlew :modules:admin-server:bootJar -x test -x allTests -x jsBrowserTest
+RUN if [ ! -f modules/admin-server/build/libs/admin-server-*.jar ]; then \
+ ./gradlew :modules:admin-server:bootJar -x test -x allTests -x jsBrowserTest; \
+ fi
-FROM openjdk:21-jdk as runner
+FROM openjdk:21-jdk
+RUN microdnf install curl
WORKDIR /app
-COPY --from=builder /app/modules/admin-server/build/libs/admin-server-0.1.2-SNAPSHOT.jar ./admin-server-0.1.2.jar
+COPY --from=builder /app/modules/admin-server/build/libs/admin-server-*.jar ./admin-server.jar
+HEALTHCHECK --interval=30s --timeout=3s CMD curl -f http://localhost:8080/status || exit 1
+
+# Create non-root user
+RUN useradd -r -u 1002 -g root admin-server
+USER admin-server
-ENTRYPOINT ["java", "-jar", "admin-server-0.1.2.jar"]
+ENTRYPOINT ["java"]
+CMD ["-XX:MaxRAMPercentage=75.0", "-XX:InitialRAMPercentage=50.0", "-XX:+UseG1GC", "-jar", "admin-server.jar"]
diff --git a/.docker/federation-server/Dockerfile b/.docker/federation-server/Dockerfile
index f8143688..30b56cca 100644
--- a/.docker/federation-server/Dockerfile
+++ b/.docker/federation-server/Dockerfile
@@ -1,18 +1,25 @@
-FROM openjdk:21-jdk as builder
+FROM openjdk:21-jdk AS builder
RUN microdnf install findutils
WORKDIR /app
-
COPY . /app
-
RUN chmod +x ./gradlew
-RUN ./gradlew :modules:federation-server:bootJar -x test -x allTests -x jsBrowserTest
+RUN if [ ! -f modules/federation-server/build/libs/federation-server-*.jar ]; then \
+ ./gradlew :modules:federation-server:bootJar -x test -x allTests -x jsBrowserTest; \
+ fi
-FROM openjdk:21-jdk as runner
+FROM openjdk:21-jdk
+RUN microdnf install curl
WORKDIR /app
-COPY --from=builder /app/modules/federation-server/build/libs/federation-server-0.1.2-SNAPSHOT.jar ./federation-server-0.1.2.jar
+COPY --from=builder /app/modules/federation-server/build/libs/federation-server-*.jar ./federation-server.jar
+HEALTHCHECK --interval=30s --timeout=3s CMD curl -f http://localhost:8080/status || exit 1
+
+# Create non-root user
+RUN useradd -r -u 1001 -g root federation-server
+USER federation-server
-ENTRYPOINT ["java", "-jar", "federation-server-0.1.2.jar"]
+ENTRYPOINT ["java"]
+CMD ["-XX:MaxRAMPercentage=75.0", "-XX:InitialRAMPercentage=50.0", "-XX:+UseG1GC", "-jar", "federation-server.jar"]
diff --git a/.githooks/pre-commit b/.githooks/pre-commit
new file mode 100755
index 00000000..d26ca4d0
--- /dev/null
+++ b/.githooks/pre-commit
@@ -0,0 +1,57 @@
+#!/usr/bin/env bash
+
+# Ensure script works on both Unix and Windows
+export MSYS=winsymlinks:nativestrict
+export MSYS2=winsymlinks:nativestrict
+
+# Get the commit message from the staged commit
+COMMIT_MSG=$(git log -1 --pretty=%B)
+
+# Get the current version from build.gradle.kts
+CURRENT_VERSION=$(grep 'version = ' build.gradle.kts | sed 's/.*version = "\(.*\)".*/\1/')
+
+# Remove -SNAPSHOT if present
+VERSION_BASE=${CURRENT_VERSION%-SNAPSHOT}
+
+# Split version into components
+IFS='.' read -r MAJOR MINOR PATCH <<< "$VERSION_BASE"
+
+# Determine version bump type based on commit message
+if [[ $COMMIT_MSG == *"BREAKING CHANGE"* ]] || [[ $COMMIT_MSG == *"!:"* ]]; then
+ # Major version bump
+ MAJOR=$((MAJOR + 1))
+ MINOR=0
+ PATCH=0
+ echo "BREAKING CHANGE detected - Bumping major version"
+elif [[ $COMMIT_MSG =~ ^feat: ]]; then
+ # Minor version bump
+ MINOR=$((MINOR + 1))
+ PATCH=0
+ echo "Feature detected - Bumping minor version"
+elif [[ $COMMIT_MSG =~ ^(fix|docs|style|refactor|perf|test|chore): ]]; then
+ # Patch version bump
+ PATCH=$((PATCH + 1))
+ echo "Fix/maintenance detected - Bumping patch version"
+else
+ # Default to patch bump if no conventional commit prefix is found
+ PATCH=$((PATCH + 1))
+ echo "No conventional commit prefix found - Defaulting to patch bump"
+fi
+
+# Construct new version
+NEW_VERSION="$MAJOR.$MINOR.$PATCH-SNAPSHOT"
+
+# Update build.gradle.kts with new version (using platform-independent sed)
+if [[ "$OSTYPE" == "msys" ]] || [[ "$OSTYPE" == "cygwin" ]]; then
+ # Windows-specific sed command
+ sed -i "s/version = \"$CURRENT_VERSION\"/version = \"$NEW_VERSION\"/" build.gradle.kts
+else
+ # Unix-like systems
+ sed -i "" "s/version = \"$CURRENT_VERSION\"/version = \"$NEW_VERSION\"/" build.gradle.kts
+fi
+
+# Add the modified build.gradle.kts to the commit
+git add build.gradle.kts
+
+# Print the version change with type
+echo "Bumped version from $CURRENT_VERSION to $NEW_VERSION"
\ No newline at end of file
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 444dc8cd..cd9eb8f9 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -1,14 +1,15 @@
name: Run CI
+
on:
push:
workflow_dispatch:
jobs:
gradle:
+ outputs:
+ success: ${{ steps.build.outcome == 'success' }}
strategy:
matrix:
- # Removed windows, because build failing with docker network. "bridge" network driver is not supported for Windows containers
- # os: [ ubuntu-latest, windows-latest ]
os: [ ubuntu-latest ]
runs-on: ${{ matrix.os }}
steps:
@@ -18,20 +19,6 @@ jobs:
distribution: temurin
java-version: 21
- - name: Run database
- run: docker compose -f docker-compose.yaml up db -d
- env:
- DATASOURCE_USER: ${{ secrets.DATASOURCE_USER }}
- DATASOURCE_PASSWORD: ${{ secrets.DATASOURCE_PASSWORD }}
- DATASOURCE_URL: ${{ secrets.DATASOURCE_URL }}
-
- - name: Run local KMS database
- run: docker compose -f docker-compose.yaml up local-kms-db -d
- env:
- DATASOURCE_USER: ${{ secrets.LOCAL_KMS_DATASOURCE_USER }}
- DATASOURCE_PASSWORD: ${{ secrets.LOCAL_KMS_DATASOURCE_PASSWORD }}
- DATASOURCE_URL: ${{ secrets.LOCAL_KMS_DATASOURCE_URL }}
-
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
@@ -39,13 +26,8 @@ jobs:
if: runner.os != 'Windows'
run: chmod +x ./gradlew
- - name: Execute Gradle build
- run: |
- ./gradlew build
- ./gradlew :modules:openapi:jsPublicPackageJson
- ./gradlew :modules:openid-federation-common:jsPublicPackageJson
- ./gradlew publishJsPackageToNpmjsRegistry
- ./gradlew publishAllPublicationsToSphereon-opensourceRepository
+ - name: Execute build
+ id: build
env:
APP_KEY: ${{ secrets.APP_KEY }}
DATASOURCE_USER: ${{ secrets.DATASOURCE_USER }}
@@ -58,3 +40,136 @@ jobs:
NEXUS_PASSWORD: ${{ secrets.NEXUS_PASSWORD }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
KMS_PROVIDER: local
+ run: |
+ ./gradlew build
+ ./gradlew :modules:openapi:jsPublicPackageJson
+ ./gradlew :modules:openid-federation-common:jsPublicPackageJson
+ ./gradlew publishJsPackageToNpmjsRegistry
+ ./gradlew publishAllPublicationsToSphereon-opensourceRepository
+
+ - name: Upload build artifacts
+ uses: actions/upload-artifact@v4
+ with:
+ name: build-artifacts
+ path: |
+ modules/federation-server/build/libs/federation-server-*.jar
+ modules/admin-server/build/libs/admin-server-*.jar
+ docker-publish:
+ needs: gradle
+ runs-on: ubuntu-latest
+ if: (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop') && (github.event_name == 'repository_dispatch' || (github.event_name == 'pull_request' && github.event.pull_request.merged == true) || (github.event_name == 'push' && needs.gradle.outputs.success == 'true'))
+ timeout-minutes: 20
+ permissions:
+ contents: write
+ actions: write
+ packages: write
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ fetch-tags: true
+ token: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Get version info
+ id: get_version_info
+ run: |
+ git config --local user.email "${GITHUB_ACTOR}@users.noreply.github.com"
+ git config --local user.name "${GITHUB_ACTOR}"
+ EVENT_NAME="${{ github.event_name }}"
+ if [[ "$EVENT_NAME" == "pull_request" ]]; then
+ BRANCH_NAME="${{ github.event.pull_request.head.ref }}"
+ else
+ BRANCH_NAME="${GITHUB_REF#refs/heads/}"
+ fi
+ GRADLE_VERSION=$(grep 'version = ' build.gradle.kts | sed 's/.*version = "\(.*\)".*/\1/')
+ GRADLE_VERSION=${GRADLE_VERSION%-SNAPSHOT}
+ COMMIT_SHA=$(git rev-parse --short HEAD)
+ PR_NUMBER=${{ github.event.pull_request.number }}
+
+ if [[ $BRANCH_NAME == "main" ]]; then
+ NEW_VERSION="v${GRADLE_VERSION}"
+ elif [[ $BRANCH_NAME == "develop" ]]; then
+ NEW_VERSION="v${GRADLE_VERSION}-beta.${COMMIT_SHA}"
+ elif [[ $BRANCH_NAME == release/* ]]; then
+ NEW_VERSION="v${GRADLE_VERSION}-rc.${COMMIT_SHA}"
+ else
+ SAFE_BRANCH=$(echo "${BRANCH_NAME}" | sed 's/[^a-zA-Z0-9]/-/g')
+ if [[ -n $PR_NUMBER ]]; then
+ NEW_VERSION="v${GRADLE_VERSION}-alpha.pr${PR_NUMBER}.${COMMIT_SHA}"
+ else
+ NEW_VERSION="v${GRADLE_VERSION}-alpha.${SAFE_BRANCH}.${COMMIT_SHA}"
+ fi
+ fi
+ echo "new_version=${NEW_VERSION}" >> $GITHUB_OUTPUT
+ git tag -a ${NEW_VERSION} -m "Release ${NEW_VERSION}"
+ git push origin ${NEW_VERSION}
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Download build artifacts
+ uses: actions/download-artifact@v4
+ with:
+ name: build-artifacts
+ path: ./
+
+ - name: Create directory structure and move artifacts
+ run: |
+ mkdir -p modules/federation-server/build/libs/
+ mkdir -p modules/admin-server/build/libs/
+ mv ./federation-server/build/libs/federation-server-*.jar modules/federation-server/build/libs/
+ mv ./admin-server/build/libs/admin-server-*.jar modules/admin-server/build/libs/
+ chmod 644 modules/federation-server/build/libs/*.jar
+ chmod 644 modules/admin-server/build/libs/*.jar
+
+ - name: Log in to Docker Hub
+ uses: docker/login-action@v3
+ with:
+ username: ${{ secrets.DOCKERHUB_USERNAME }}
+ password: ${{ secrets.DOCKERHUB_TOKEN }}
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v3
+
+ - name: Extract metadata (federation-server)
+ id: meta-federation
+ uses: docker/metadata-action@v5
+ with:
+ images: sphereon/openid-federation-server
+ tags: |
+ type=raw,value=latest,enable={{is_default_branch}}
+ type=semver,pattern={{version}},value=${{ steps.get_version_info.outputs.new_version }}
+
+ - name: Build and push federation-server
+ uses: docker/build-push-action@v5
+ with:
+ context: .
+ file: ./.docker/federation-server/Dockerfile
+ push: true
+ tags: ${{ steps.meta-federation.outputs.tags }}
+ labels: ${{ steps.meta-federation.outputs.labels }}
+ cache-from: |
+ type=registry,ref=${{ secrets.DOCKERHUB_USERNAME }}/openid-federation-server:latest
+ type=registry,ref=${{ secrets.DOCKERHUB_USERNAME }}/openid-federation-base:latest
+ - name: Extract metadata (admin-server)
+ id: meta-admin
+ uses: docker/metadata-action@v5
+ with:
+ images: sphereon/openid-federation-admin-server
+ tags: |
+ type=raw,value=latest,enable={{is_default_branch}}
+ type=semver,pattern={{version}},value=${{ steps.get_version_info.outputs.new_version }}
+ - name: Build and push admin-server
+ uses: docker/build-push-action@v5
+ with:
+ context: .
+ file: ./.docker/admin-server/Dockerfile
+ push: true
+ tags: ${{ steps.meta-admin.outputs.tags }}
+ labels: ${{ steps.meta-admin.outputs.labels }}
+ cache-from: |
+ type=registry,ref=${{ secrets.DOCKERHUB_USERNAME }}/openid-federation-admin-server:latest
+ type=registry,ref=${{ secrets.DOCKERHUB_USERNAME }}/openid-federation-base:latest
+ cache-to: |
+ type=registry,ref=${{ secrets.DOCKERHUB_USERNAME }}/openid-federation-admin-server:latest,mode=max
+ type=registry,ref=${{ secrets.DOCKERHUB_USERNAME }}/openid-federation-base:latest,mode=max
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 3da44e6b..bab80ef2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -26,3 +26,9 @@ kotlin-js-store/
.env.local
/.docker/keycloak-dev/
/modules/admin-server/logs/
+/logs/*
+/logs/admin-server/*
+/logs/federation-server/*
+!logs/.gitkeep
+!logs/admin-server/.gitkeep
+!logs/federation-server/.gitkeep
\ No newline at end of file
diff --git a/README.md b/README.md
index 14909c2b..77f348e7 100644
--- a/README.md
+++ b/README.md
@@ -3,8 +3,16 @@
OpenID Federation Monorepo
+
+
+
+[](https://hub.docker.com/r/sphereon/openid-federation-server)
+[](LICENSE)
+
+
+
# Background
OpenID Federation is a framework designed to facilitate the secure and interoperable interaction of entities within a
@@ -255,7 +263,8 @@ To create a new tenant account, follow these steps:
}
```
-Note: All subsequent requests will use the `X-Account-Username` header to specify the account context. If not provided, it defaults to the root account.
+Note: All subsequent requests will use the `X-Account-Username` header to specify the account context. If not provided,
+it defaults to the root account.
## Step 5: Delete a Tenant Account
@@ -267,6 +276,7 @@ To delete a tenant account, follow these steps:
DELETE http://localhost:8081/accounts
X-Account-Username: {username} # root account cannot be deleted
```
+
## Step 6: Create and Manage Keys
### Create a New Key Pair
@@ -346,6 +356,7 @@ To assign metadata to your entity, follow these steps:
DELETE http://localhost:8081/metadata/{id}
X-Account-Username: {username} # Optional, defaults to root
```
+
---
## Step 8: Create and Manage Subordinates
@@ -384,6 +395,7 @@ To assign metadata to your entity, follow these steps:
DELETE http://localhost:8081/subordinates/{id}
X-Account-Username: {username} # Optional, defaults to root
```
+
---
## Step 9: Manage Subordinate Metadata
@@ -423,7 +435,9 @@ To assign metadata to your entity, follow these steps:
DELETE http://localhost:8081/subordinates/{subordinateId}/metadata/{id}
X-Account-Username: {username} # Optional, defaults to root
```
+
---
+
## Step 10: Manage Subordinate JWKS
### Add a JWKS for a Subordinate
@@ -464,6 +478,7 @@ To assign metadata to your entity, follow these steps:
```
---
+
## Step 11: Get Subordinate Statement Object
1. Send a `GET` request to retrieve the statement for a subordinate:
@@ -514,13 +529,15 @@ To assign metadata to your entity, follow these steps:
X-Account-Username: {username} # Optional, defaults to root
```
-2. Optionally, include a `dryRun` parameter in the request body to test the statement publication without making changes:
+2. Optionally, include a `dryRun` parameter in the request body to test the statement publication without making
+ changes:
```json
{
"dryRun": true
}
```
+
# Trust Marks
## Trust Mark Workflow
@@ -624,10 +641,11 @@ GET http://localhost:8080/trust-mark-issuer/trust-mark-status
"sub": "https://example.com/holder"
}
```
+
# API Reference
For the complete API documentation, please
-visit [the API Reference](https://github.com/Sphereon-Opensource/OpenID-Federation/)
+visit [the API Reference](https://app.swaggerhub.com/apis-docs/SphereonInt/OpenIDFederationAPI)
# License
diff --git a/build.gradle.kts b/build.gradle.kts
index 80cc70bc..3e526485 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -1,6 +1,60 @@
+import java.io.ByteArrayOutputStream
+
+tasks.register("installGitHooks", Copy::class) {
+ group = "git hooks"
+ description = "Installs git hooks from .githooks directory"
+
+ val sourceDir = file(".githooks")
+ val targetDir = file(".git/hooks")
+
+ from(sourceDir) {
+ include("**/*")
+ }
+ into(targetDir)
+ fileMode = 0b111101101 // 755 in octal: rwxr-xr-x
+
+ inputs.dir(sourceDir)
+ outputs.dir(targetDir)
+
+ doFirst {
+ sourceDir.mkdirs()
+ targetDir.mkdirs()
+
+ val preCommitFile = sourceDir.resolve("pre-commit")
+ if (!preCommitFile.exists()) {
+ throw GradleException("pre-commit hook file not found in .githooks directory")
+ }
+
+ println("Installing Git hooks...")
+ }
+
+ outputs.upToDateWhen {
+ val preCommitSource = sourceDir.resolve("pre-commit")
+ val preCommitTarget = targetDir.resolve("pre-commit")
+
+ if (!preCommitTarget.exists()) {
+ return@upToDateWhen false
+ }
+
+ val isUpToDate = preCommitSource.lastModified() <= preCommitTarget.lastModified() &&
+ preCommitSource.length() == preCommitTarget.length()
+
+ isUpToDate
+ }
+}
+
+tasks.matching { it.name == "build" }.configureEach {
+ dependsOn("installGitHooks")
+}
+
+gradle.projectsEvaluated {
+ tasks.named("prepareKotlinBuildScriptModel").configure {
+ dependsOn("installGitHooks")
+ }
+}
+
+
plugins {
- // this is necessary to avoid the plugins to be loaded multiple times
- // in each subproject's classloader
alias(libs.plugins.androidApplication) apply false
alias(libs.plugins.androidLibrary) apply false
alias(libs.plugins.jetbrainsCompose) apply false
@@ -39,7 +93,7 @@ fun getNpmVersion(): String {
allprojects {
group = "com.sphereon.oid.fed"
- version = "0.1.2-SNAPSHOT"
+ version = "0.4.4-SNAPSHOT"
val npmVersion by extra { getNpmVersion() }
// Common repository configuration for all projects
@@ -65,6 +119,128 @@ subprojects {
}
}
}
+
+ // Ensure unique coordinates for different publication types
+ publications.withType {
+ val publicationName = name
+ if (publicationName == "kotlinMultiplatform") {
+ artifactId = "${project.name}-multiplatform"
+ } else if (publicationName == "mavenKotlin") {
+ artifactId = "${project.name}-jvm"
+ }
+ }
+ }
+ }
+}
+
+tasks.register("checkDockerStatus") {
+ group = "docker"
+ description = "Checks if Docker containers are running"
+ doLast {
+ val output = ByteArrayOutputStream()
+ val process = exec {
+ commandLine("docker", "compose", "ps", "-q", "db", "local-kms-db")
+ isIgnoreExitValue = true
+ standardOutput = output
+ }
+
+ val containersRunning = process.exitValue == 0 && output.toString().trim().isNotEmpty()
+ project.ext.set("containersRunning", containersRunning)
+
+ if (containersRunning) {
+ println("Required Docker containers are already running")
+ } else {
+ println("Required Docker containers are not running")
+ }
+ }
+}
+
+tasks.register("dockerCleanup") {
+ group = "docker"
+ description = "Stops and removes specific Docker containers"
+ doLast {
+ exec {
+ commandLine("docker", "compose", "rm", "-fsv", "db", "local-kms-db")
+ }
+ }
+}
+
+fun waitForDatabase() {
+ var ready = false
+ var attempts = 0
+ val maxAttempts = 30
+
+ while (!ready && attempts < maxAttempts) {
+ try {
+ val process = exec {
+ commandLine("docker", "compose", "exec", "-T", "db", "pg_isready", "-U", "postgres")
+ isIgnoreExitValue = true
+ }
+ ready = process.exitValue == 0
+ } catch (e: Exception) {
+ }
+
+ if (!ready) {
+ attempts++
+ Thread.sleep(2000)
+ println("Waiting for database to be ready... (Attempt $attempts/$maxAttempts)")
+ }
+ }
+
+ if (!ready) {
+ throw GradleException("Database failed to become ready within the timeout period")
+ }
+ println("Database is ready!")
+}
+
+tasks.register("waitForDatabase") {
+ group = "docker"
+ description = "Waits for the database to be ready"
+ doLast {
+ waitForDatabase()
+ }
+}
+
+tasks.register("dockerStart") {
+ group = "docker"
+ description = "Starts specific Docker containers"
+ doLast {
+ exec {
+ commandLine("docker", "compose", "up", "-d", "db", "local-kms-db")
+ }
+ waitForDatabase()
+ }
+}
+
+tasks.register("dockerEnsureRunning") {
+ group = "docker"
+ description = "Ensures Docker containers are running, starting them if needed"
+ dependsOn("checkDockerStatus")
+
+ doLast {
+ if (!project.ext.has("containersRunning") || !project.ext.get("containersRunning").toString().toBoolean()) {
+ exec {
+ commandLine("docker", "compose", "rm", "-fsv", "db", "local-kms-db")
+ }
+ exec {
+ commandLine("docker", "compose", "up", "-d", "db", "local-kms-db")
+ }
}
+ waitForDatabase()
+ project.ext.set("containersRunning", true)
}
}
+
+subprojects {
+ tasks.matching { it.name == "build" }.configureEach {
+ dependsOn(rootProject.tasks.named("dockerEnsureRunning"))
+
+ doFirst {
+ if (!rootProject.ext.has("containersRunning") || !rootProject.ext.get("containersRunning").toString()
+ .toBoolean()
+ ) {
+ throw GradleException("Docker containers are not running. Please run './gradlew dockerEnsureRunning' first.")
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/docker-compose.yaml b/docker-compose.yaml
index 76f35e3a..21fcffd8 100644
--- a/docker-compose.yaml
+++ b/docker-compose.yaml
@@ -2,6 +2,9 @@ services:
db:
image: postgres:latest
container_name: openid-federation-datastore
+ user: postgres
+ security_opt:
+ - no-new-privileges:true
environment:
POSTGRES_USER: ${DATASOURCE_USER}
POSTGRES_PASSWORD: ${DATASOURCE_PASSWORD}
@@ -12,6 +15,12 @@ services:
- postgres_data:/var/lib/postgresql/data
networks:
- openid_network
+ restart: unless-stopped
+ logging:
+ driver: "json-file"
+ options:
+ max-size: "10m"
+ max-file: "3"
healthcheck:
test: [ "CMD-SHELL", "pg_isready -d ${DATASOURCE_DB} -U ${DATASOURCE_USER}" ]
interval: 3s
@@ -21,6 +30,9 @@ services:
local-kms-db:
image: postgres:latest
container_name: openid-federation-local-kms-datastore
+ user: postgres
+ security_opt:
+ - no-new-privileges:true
environment:
POSTGRES_USER: ${LOCAL_KMS_DATASOURCE_USER}
POSTGRES_PASSWORD: ${LOCAL_KMS_DATASOURCE_PASSWORD}
@@ -31,19 +43,29 @@ services:
- local_kms_data:/var/lib/postgresql/data
networks:
- openid_network
+ depends_on:
+ db:
+ condition: service_healthy
healthcheck:
test: [ "CMD-SHELL", "pg_isready -d ${LOCAL_KMS_DATASOURCE_DB} -U ${LOCAL_KMS_DATASOURCE_USER}" ]
interval: 3s
timeout: 5s
retries: 20
-
federation-server:
+ image: sphereon/openid-federation-server:latest
build:
context: .
dockerfile: ./.docker/federation-server/Dockerfile
+ deploy:
+ resources:
+ limits:
+ cpus: '1'
+ memory: 1G
+ reservations:
+ cpus: '0.5'
+ memory: 512M
ports:
- "8080:8080"
- container_name: openid-federation-server
environment:
DATASOURCE_URL: ${DATASOURCE_URL}
DATASOURCE_USER: ${DATASOURCE_USER}
@@ -56,20 +78,38 @@ services:
LOCAL_KMS_DATASOURCE_DB: ${LOCAL_KMS_DATASOURCE_DB}
ROOT_IDENTIFIER: ${ROOT_IDENTIFIER}
depends_on:
- admin-server:
- condition: service_started
db:
condition: service_healthy
+ local-kms-db:
+ condition: service_healthy
+ restart: unless-stopped
+ logging:
+ driver: "json-file"
+ options:
+ max-size: "10m"
+ max-file: "3"
+ volumes:
+ - ./logs/federation-server:/tmp/logs
+ user: root
+ entrypoint: >
+ /bin/sh -c "java -jar /app/federation-server.jar"
networks:
- openid_network
-
admin-server:
+ image: sphereon/openid-federation-admin-server:latest
build:
context: .
dockerfile: ./.docker/admin-server/Dockerfile
+ deploy:
+ resources:
+ limits:
+ cpus: '1'
+ memory: 1G
+ reservations:
+ cpus: '0.5'
+ memory: 512M
ports:
- "8081:8080"
- container_name: openid-federation-server-admin
environment:
DATASOURCE_URL: ${DATASOURCE_URL}
DATASOURCE_USER: ${DATASOURCE_USER}
@@ -87,25 +127,49 @@ services:
condition: service_healthy
local-kms-db:
condition: service_healthy
- keycloak:
- condition: service_healthy
networks:
- openid_network
-
+ logging:
+ driver: "json-file"
+ options:
+ max-size: "10m"
+ max-file: "3"
+ volumes:
+ - ./logs/admin-server:/tmp/logs
+ user: root
+ entrypoint: >
+ /bin/sh -c "java -jar /app/admin-server.jar"
keycloak:
image: keycloak/keycloak:26.0
+ user: keycloak
+ security_opt:
+ - no-new-privileges:true
command:
- start-dev
- --import-realm
+ deploy:
+ resources:
+ limits:
+ cpus: '1'
+ memory: 1G
+ reservations:
+ cpus: '0.5'
+ memory: 512M
ports:
- "8082:8080"
environment:
- KC_BOOTSTRAP_ADMIN_USERNAME=${KC_BOOTSTRAP_ADMIN_USERNAME}
- KC_BOOTSTRAP_ADMIN_PASSWORD=${KC_BOOTSTRAP_ADMIN_PASSWORD}
- KC_HEALTH_ENABLED=true
+ - JAVA_OPTS=-XX:MaxRAMPercentage=75.0 -XX:InitialRAMPercentage=50.0 -XX:+UseG1GC
volumes:
- ./.docker/keycloak:/opt/keycloak/data/import/
- restart: always
+ restart: unless-stopped
+ logging:
+ driver: "json-file"
+ options:
+ max-size: "10m"
+ max-file: "3"
networks:
- openid_network
healthcheck:
@@ -122,3 +186,4 @@ networks:
volumes:
postgres_data:
local_kms_data:
+ logs:
\ No newline at end of file
diff --git a/gradlew b/gradlew
old mode 100644
new mode 100755
diff --git a/init.gradle.kts b/init.gradle.kts
new file mode 100644
index 00000000..08bb49c1
--- /dev/null
+++ b/init.gradle.kts
@@ -0,0 +1,25 @@
+// This script runs during Gradle initialization
+gradle.projectsLoaded {
+ rootProject.afterEvaluate {
+ // Check if .git directory exists (we're in a Git repo)
+ if (file(".git").exists()) {
+ // Create .githooks directory if it doesn't exist
+ file(".githooks").mkdirs()
+
+ // Copy the pre-commit hook if it exists
+ val preCommitHook = file(".githooks/pre-commit")
+ if (preCommitHook.exists()) {
+ // Copy hook to .git/hooks
+ file(".git/hooks").mkdirs()
+ preCommitHook.copyTo(file(".git/hooks/pre-commit"), overwrite = true)
+
+ // Make the hook executable on Unix-like systems
+ if (!System.getProperty("os.name").lowercase().contains("windows")) {
+ Runtime.getRuntime().exec("chmod +x ${file(".git/hooks/pre-commit").absolutePath}")
+ }
+
+ println("Git pre-commit hook installed successfully")
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/logs/.gitkeep b/logs/.gitkeep
new file mode 100644
index 00000000..e69de29b
diff --git a/logs/admin-server/.gitkeep b/logs/admin-server/.gitkeep
new file mode 100644
index 00000000..e69de29b
diff --git a/logs/federation-server/.gitkeep b/logs/federation-server/.gitkeep
new file mode 100644
index 00000000..e69de29b
diff --git a/modules/admin-server/src/main/kotlin/com/sphereon/oid/fed/server/admin/Application.kt b/modules/admin-server/src/main/kotlin/com/sphereon/oid/fed/server/admin/Application.kt
index fa48d02a..0d8f341f 100644
--- a/modules/admin-server/src/main/kotlin/com/sphereon/oid/fed/server/admin/Application.kt
+++ b/modules/admin-server/src/main/kotlin/com/sphereon/oid/fed/server/admin/Application.kt
@@ -18,7 +18,7 @@ import java.time.format.DateTimeFormatter
class Application(private val logService: LogService) {
@PostConstruct
fun configureLogger() {
- val logDir = File("logs").apply { mkdirs() }
+ val logDir = File("/tmp/logs").apply { mkdirs() }
val logFile =
File(logDir, "federation-${LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"))}.log")
diff --git a/modules/admin-server/src/main/kotlin/com/sphereon/oid/fed/server/admin/handlers/logger/FileLoggerHandler.kt b/modules/admin-server/src/main/kotlin/com/sphereon/oid/fed/server/admin/handlers/logger/FileLoggerHandler.kt
index 804f1f83..2510f8fc 100644
--- a/modules/admin-server/src/main/kotlin/com/sphereon/oid/fed/server/admin/handlers/logger/FileLoggerHandler.kt
+++ b/modules/admin-server/src/main/kotlin/com/sphereon/oid/fed/server/admin/handlers/logger/FileLoggerHandler.kt
@@ -2,31 +2,72 @@ package com.sphereon.oid.fed.server.admin.handlers.logger
import com.sphereon.oid.fed.logger.Logger
import java.io.File
+import java.nio.file.Files
class FileLoggerHandler(private val logFile: File) : Logger.LogWriter {
init {
try {
+ println("Attempting to initialize log file at: ${logFile.absolutePath}")
+
logFile.parentFile?.let { parent ->
- if (!parent.exists() && !parent.mkdirs()) {
- throw IllegalStateException("Failed to create log directory: ${parent.absolutePath}")
+ if (!parent.exists()) {
+ println("Log directory doesn't exist, attempting to create: ${parent.absolutePath}")
+ try {
+ Files.createDirectories(parent.toPath())
+ println("Successfully created log directory")
+ } catch (e: Exception) {
+ val msg = "Failed to create log directory: ${parent.absolutePath}. Error: ${e.message}"
+ println(msg)
+ throw IllegalStateException(msg, e)
+ }
+ }
+
+ // Check directory permissions
+ if (!parent.canWrite()) {
+ val msg = "No write permission for log directory: ${parent.absolutePath}"
+ println(msg)
+ throw IllegalStateException(msg)
}
}
- if (!logFile.exists() && !logFile.createNewFile()) {
- throw IllegalStateException("Failed to create log file: ${logFile.absolutePath}")
+
+ if (!logFile.exists()) {
+ println("Log file doesn't exist, attempting to create: ${logFile.absolutePath}")
+ try {
+ logFile.createNewFile()
+ println("Successfully created log file")
+ } catch (e: Exception) {
+ val msg = "Failed to create log file: ${logFile.absolutePath}. Error: ${e.message}"
+ println(msg)
+ throw IllegalStateException(msg, e)
+ }
}
+
if (!logFile.canWrite()) {
- throw IllegalStateException("Log file is not writable: ${logFile.absolutePath}")
+ val msg = "Log file is not writable: ${logFile.absolutePath}"
+ println(msg)
+ throw IllegalStateException(msg)
}
+
+ println("Successfully initialized log file handler")
} catch (e: SecurityException) {
- throw IllegalStateException("Security violation while setting up log file: ${logFile.absolutePath}", e)
+ val msg = "Security violation while setting up log file: ${logFile.absolutePath}"
+ println("$msg. Error: ${e.message}")
+ throw IllegalStateException(msg, e)
} catch (e: Exception) {
- throw IllegalStateException("Failed to initialize log file: ${logFile.absolutePath}", e)
+ val msg = "Failed to initialize log file: ${logFile.absolutePath}"
+ println("$msg. Error: ${e.message}")
+ throw IllegalStateException(msg, e)
}
}
override fun log(event: Logger.LogEvent) {
synchronized(this) {
- logFile.appendText("${event.formattedMessage}\n")
+ try {
+ logFile.appendText("${event.formattedMessage}\n")
+ } catch (e: Exception) {
+ println("Failed to write to log file: ${logFile.absolutePath}. Error: ${e.message}")
+ // Consider implementing a fallback logging mechanism here
+ }
}
}
-}
+}
\ No newline at end of file
diff --git a/modules/admin-server/src/main/kotlin/com/sphereon/oid/fed/server/admin/middlewares/LoggerMiddleware.kt b/modules/admin-server/src/main/kotlin/com/sphereon/oid/fed/server/admin/middlewares/LoggerMiddleware.kt
index e4374618..90b7f631 100644
--- a/modules/admin-server/src/main/kotlin/com/sphereon/oid/fed/server/admin/middlewares/LoggerMiddleware.kt
+++ b/modules/admin-server/src/main/kotlin/com/sphereon/oid/fed/server/admin/middlewares/LoggerMiddleware.kt
@@ -44,7 +44,7 @@ class LoggerMiddleware : OncePerRequestFilter() {
}
private fun getOperationName(request: HttpServletRequest): String {
- val baseResource = request.requestURI.split("/")[1].capitalize()
+ val baseResource = request.requestURI.split("/")[1]
return when (request.method.uppercase()) {
"GET" -> "$baseResource Retrieval"
"POST" -> "$baseResource Creation"
diff --git a/modules/local-kms/build.gradle.kts b/modules/local-kms/build.gradle.kts
index 31552972..3c7210e3 100644
--- a/modules/local-kms/build.gradle.kts
+++ b/modules/local-kms/build.gradle.kts
@@ -58,18 +58,15 @@ kotlin {
}
publishing {
- publications {
- create("mavenKotlin") {
-
- pom {
- name.set("OpenID Federation Local KMS")
- description.set("Local Key Management System for OpenID Federation")
- url.set("https://github.com/Sphereon-Opensource/openid-federation")
- licenses {
- license {
- name.set("The Apache License, Version 2.0")
- url.set("http://www.apache.org/licenses/LICENSE-2.0.txt")
- }
+ publications.withType().configureEach {
+ pom {
+ name.set("OpenID Federation Local KMS")
+ description.set("Local Key Management System for OpenID Federation")
+ url.set("https://github.com/Sphereon-Opensource/openid-federation")
+ licenses {
+ license {
+ name.set("The Apache License, Version 2.0")
+ url.set("http://www.apache.org/licenses/LICENSE-2.0.txt")
}
}
}
diff --git a/modules/services/src/commonMain/kotlin/com/sphereon/oid/fed/services/config/AccountServiceConfig.kt b/modules/services/src/commonMain/kotlin/com/sphereon/oid/fed/services/config/AccountServiceConfig.kt
index 70baf81b..97291441 100644
--- a/modules/services/src/commonMain/kotlin/com/sphereon/oid/fed/services/config/AccountServiceConfig.kt
+++ b/modules/services/src/commonMain/kotlin/com/sphereon/oid/fed/services/config/AccountServiceConfig.kt
@@ -3,6 +3,5 @@ package com.sphereon.oid.fed.services.config
/**
* Configuration class for account-related settings.
*/
-expect class AccountServiceConfig(rootIdentifier: String = "default-root") : IAccountServiceConfig {
- override val rootIdentifier: String
+class AccountServiceConfig(override val rootIdentifier: String = "default-root") : IAccountServiceConfig {
}
\ No newline at end of file
diff --git a/modules/services/src/jvmMain/kotlin/com/sphereon/oid/fed/services/config/AccountServiceConfig.kt b/modules/services/src/jvmMain/kotlin/com/sphereon/oid/fed/services/config/AccountServiceConfig.kt
deleted file mode 100644
index ed4c9895..00000000
--- a/modules/services/src/jvmMain/kotlin/com/sphereon/oid/fed/services/config/AccountServiceConfig.kt
+++ /dev/null
@@ -1,8 +0,0 @@
-package com.sphereon.oid.fed.services.config
-
-/**
- * JVM implementation of account configuration.
- */
-actual class AccountServiceConfig actual constructor(
- actual override val rootIdentifier: String
-) : IAccountServiceConfig
\ No newline at end of file
diff --git a/modules/services/src/jvmMain/resources/application.yml b/modules/services/src/jvmMain/resources/application.yml
deleted file mode 100644
index f21bfa50..00000000
--- a/modules/services/src/jvmMain/resources/application.yml
+++ /dev/null
@@ -1,3 +0,0 @@
-sphereon:
- federation:
- root-identifier: http://localhost:8081
\ No newline at end of file