Author: Lisa
SPIRE Setup Documentation
Overview
This setup deploys SPIRE as follows:
- SPIRE Server on <SPIRE_SERVER_HOST>
- SPIRE Agent on <SPIRE_AGENT_HOST>
- Trust domain: <TRUST_DOMAIN>
- Server/agent communication port: <SERVER_PORT>/tcp
How it works
SPIRE provides machine and workload identity.
The SPIRE Server on <SPIRE_SERVER_HOST> is the trust authority for the trust domain <TRUST_DOMAIN>.
The SPIRE Agent on <SPIRE_AGENT_HOST> attests to the server using x509pop with an X.509 certificate issued by an enterprise/internal CA.
Applications on <SPIRE_AGENT_HOST> do not talk directly to the SPIRE Server. They talk to the local SPIRE Agent over the local workload API socket <AGENT_SOCKET_PATH>.
The agent returns an X.509-SVID representing the workload identity.
Installation Instructions
- Install SPIRE binaries
Run on both hosts:
mkdir -p /opt/spire
cd /tmp
wget https://github.com/spiffe/spire/releases/download/v1.15.2/spire-1.15.2-linux-amd64-musl.tar.gz
tar zxf spire-1.15.2-linux-amd64-musl.tar.gz
cp -r spire-1.15.2/. /opt/spire/
On the SPIRE Server host:
ln -sf /opt/spire/bin/spire-server /usr/bin/spire-server
On the SPIRE Agent host:
ln -sf /opt/spire/bin/spire-agent /usr/bin/spire-agent
- Configure SPIRE Server
Create directories:
mkdir -p /opt/spire/conf
mkdir -p /opt/spire/data/server
mkdir -p /opt/spire/conf/x509pop
Place the CA bundle at:
/opt/spire/conf/x509pop/bundle.pem
Contents:
—–BEGIN CERTIFICATE—–
<REDACTED CA CERTIFICATE>
—–END CERTIFICATE—–
—–BEGIN CERTIFICATE—–
<REDACTED CA CERTIFICATE>
—–END CERTIFICATE—–
—–BEGIN CERTIFICATE—–
<REDACTED CA CERTIFICATE>
—–END CERTIFICATE—–
Create /opt/spire/conf/server.conf:
server {
bind_address = “0.0.0.0”
bind_port = “<SERVER_PORT>”
trust_domain = “<TRUST_DOMAIN>”
data_dir = “/opt/spire/data/server”
log_level = “INFO”
}
plugins {
DataStore “sql” {
plugin_data {
database_type = “sqlite3”
connection_string = “/opt/spire/data/server/datastore.sqlite3”
}
}
NodeAttestor “x509pop” {
plugin_data {
ca_bundle_path = “/opt/spire/conf/x509pop/bundle.pem”
}
}
KeyManager “memory” {
plugin_data {}
}
}
health_checks {
listener_enabled = true
bind_address = “127.0.0.1”
bind_port = “<HEALTH_PORT>”
}
Create /etc/systemd/system/spire-server.service:
[Unit]
Description=SPIRE Server
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
ExecStart=/opt/spire/bin/spire-server run -config /opt/spire/conf/server.conf
Restart=on-failure
RestartSec=5
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target
Start server:
systemctl daemon-reload
systemctl enable –now spire-server
systemctl status spire-server –no-pager
Validate server:
spire-server healthcheck
- Configure SPIRE Agent
Create directories:
mkdir -p /opt/spire/conf
mkdir -p /opt/spire/data/agent
mkdir -p /opt/spire/sockets
mkdir -p /opt/spire/conf/x509pop
Issue a certificate through your enterprise PKI platform. Download as OpenSSL format and split CRT/KEY files. Copy the node certificate to:
/opt/spire/conf/x509pop/agent.crt
Copy the node private key to:
/opt/spire/conf/x509pop/agent.key
The private key must be unencrypted PEM.
Set permissions:
chmod 700 /opt/spire/conf/x509pop
chmod 600 /opt/spire/conf/x509pop/agent.key
chmod 644 /opt/spire/conf/x509pop/agent.crt
Create /opt/spire/conf/agent.conf:
agent {
data_dir = “/opt/spire/data/agent”
log_level = “INFO”
trust_domain = “<TRUST_DOMAIN>”
server_address = “<SPIRE_SERVER_HOST>”
server_port = “<SERVER_PORT>”
socket_path = “<AGENT_SOCKET_PATH>”
insecure_bootstrap = true
}
plugins {
KeyManager “disk” {
plugin_data {
directory = “/opt/spire/data/agent”
}
}
WorkloadAttestor “unix” {
plugin_data {}
}
NodeAttestor “x509pop” {
plugin_data {
private_key_path = “/opt/spire/conf/x509pop/agent.key”
certificate_path = “/opt/spire/conf/x509pop/agent.crt”
}
}
}
Create /etc/systemd/system/spire-agent.service:
[Unit]
Description=SPIRE Agent
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
ExecStart=/opt/spire/bin/spire-agent run -config /opt/spire/conf/agent.conf
Restart=on-failure
RestartSec=5
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target
Start agent:
systemctl daemon-reload
systemctl enable –now spire-agent
systemctl status spire-agent –no-pager
- Validate x509pop agent attestation
On the SPIRE Server host:
spire-server agent list
Expected result:
- Agent attestation type is x509pop
- Can re-attest is true
- Parent ID format resembles:
spiffe://<TRUST_DOMAIN>/spire/agent/x509pop/<AGENT_HASH>
- Create workload registration entry
Use the current x509pop agent SPIFFE ID from spire-server agent list.
On the SPIRE Server host:
spire-server entry create \
-spiffeID spiffe://<TRUST_DOMAIN>/workload/<WORKLOAD_NAME> \
-parentID spiffe://<TRUST_DOMAIN>/spire/agent/x509pop/<AGENT_HASH> \
-selector unix:uid:0
This authorizes a root-owned process on the SPIRE Agent host.
- Fetch workload identity on the SPIRE Agent host
/opt/spire/bin/spire-agent api fetch x509 -socketPath <AGENT_SOCKET_PATH>
Expected SPIFFE ID:
spiffe://<TRUST_DOMAIN>/workload/<WORKLOAD_NAME>
- Write certs to disk for testing
Create destination directory:
mkdir -p /etc/spire/svid/test
chmod 700 /etc/spire/svid/test
Write files:
/opt/spire/bin/spire-agent api fetch x509 \
-socketPath <AGENT_SOCKET_PATH> \
-write /etc/spire/svid/test
Inspect output:
ls -l /etc/spire/svid/test
openssl x509 -in /etc/spire/svid/test/svid.0.pem -text -noout
- Reboot persistence validation
Reboot the SPIRE Agent host.
After reboot, validate:
systemctl status spire-agent –no-pager
ls -l <AGENT_SOCKET_PATH>
/opt/spire/bin/spire-agent api fetch x509 -socketPath <AGENT_SOCKET_PATH>
Expected behavior:
- spire-agent starts automatically
- Workload API socket exists
- X.509-SVID fetch succeeds
- Operational notes
- Current architecture: <SPIRE_SERVER_HOST> = SPIRE Server; <SPIRE_AGENT_HOST> = SPIRE Agent
- Current trust domain: <TRUST_DOMAIN>
- Current server/agent path: <SPIRE_AGENT_HOST> to <SPIRE_SERVER_HOST> on TCP <SERVER_PORT>
- Current Workload API socket: <AGENT_SOCKET_PATH>
- Current example workload selector: unix:uid:0
- Current example workload identity: spiffe://<TRUST_DOMAIN>/workload/<WORKLOAD_NAME>
JWT
Create JWT registration on the SPIRE Server host:
spire-server entry create \
-spiffeID spiffe://<TRUST_DOMAIN>/workload/<JWT_WORKLOAD_NAME> \
-parentID spiffe://<TRUST_DOMAIN>/spire/agent/x509pop/<AGENT_HASH> \
-selector unix:uid:0
Fetch JWT-SVID from the SPIRE Agent host:
/opt/spire/bin/spire-agent api fetch jwt \
-socketPath <AGENT_SOCKET_PATH> \
-audience <JWT_AUDIENCE> \
-spiffeID spiffe://<TRUST_DOMAIN>/workload/<JWT_WORKLOAD_NAME>
Example output:
token(spiffe://<TRUST_DOMAIN>/workload/<JWT_WORKLOAD_NAME>):
<REDACTED JWT-SVID>
bundle(spiffe://<TRUST_DOMAIN>):
<REDACTED JWKS BUNDLE>
SPIRE OIDC Discovery Provider
On the SPIRE Server host:
mkdir -p /opt/spire-extras
cd /tmp
wget https://github.com/spiffe/spire/releases/download/v1.15.2/spire-extras-1.15.2-linux-amd64-musl.tar.gz
tar zxf spire-extras-1.15.2-linux-amd64-musl.tar.gz
cp -r spire-extras-1.15.2/ /opt/spire-extras/
ln -sf /opt/spire-extras/bin/oidc-discovery-provider /usr/bin/oidc-discovery-provider
mkdir -p /opt/spire-extras/conf/oidc-discovery-provider
Create certificate and key files:
/opt/spire-extras/conf/oidc-discovery-provider/tls.crt
/opt/spire-extras/conf/oidc-discovery-provider/tls.key
Set permissions:
chmod 644 /opt/spire-extras/conf/oidc-discovery-provider/tls.crt
chmod 600 /opt/spire-extras/conf/oidc-discovery-provider/tls.key
Create /opt/spire-extras/conf/oidc-discovery-provider/oidc-discovery-provider.conf:
log_level = “INFO”
domains = [“<OIDC_DISCOVERY_DOMAIN>”]
server_api {
address = “unix://<SPIRE_SERVER_API_SOCKET>”
}
serving_cert_file {
cert_file_path = “/opt/spire-extras/conf/oidc-discovery-provider/tls.crt”
key_file_path = “/opt/spire-extras/conf/oidc-discovery-provider/tls.key”
}
Create /etc/systemd/system/spire-oidc-discovery-provider.service:
[Unit]
Description=SPIRE OIDC Discovery Provider
After=network-online.target spire-server.service
Wants=network-online.target
[Service]
Type=simple
ExecStart=/opt/spire-extras/bin/oidc-discovery-provider -config /opt/spire-extras/conf/oidc-discovery-provider/oidc-discovery-provider.conf
Restart=on-failure
RestartSec=5
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target
Ping Integration
PingFederate Integration Note for SPIRE JWT Validation
Purpose
Configure PingFederate to trust and validate JWTs issued from the SPIRE environment.
SPIRE issuer details
Use these values:
- Issuer: https://<OIDC_DISCOVERY_DOMAIN>
- OIDC discovery URL: https://<OIDC_DISCOVERY_DOMAIN>/.well-known/openid-configuration
- JWKS URL: https://<OIDC_DISCOVERY_DOMAIN>/keys
Trust model
PingFederate should validate JWT signatures using the JWKS published by the SPIRE OIDC Discovery Provider.
Ping does not need to call the SPIRE server directly for every token validation. It should use the discovery/JWKS metadata from the OIDC Discovery Provider.
Expected JWT characteristics
Issuer
Ping should require:
iss = https://<OIDC_DISCOVERY_DOMAIN>
Audience
Recommended audience value:
<PING_AUDIENCE>
Clients requesting JWT-SVIDs from SPIRE should request them with this audience.
Subject
The workload identity will be in:
sub
Example:
spiffe://<TRUST_DOMAIN>/workload/<WORKLOAD_NAME>
This is the primary identity claim Ping should use to identify the calling workload.
Recommended validation rules in Ping
Validate:
- JWT signature against SPIRE JWKS
- iss matches https://<OIDC_DISCOVERY_DOMAIN>
- aud contains <PING_AUDIENCE>
- token is within validity window (exp, iat)
- sub is an allowed SPIFFE ID or matches allowed policy rules
Example workload identity currently in use
Current example SPIFFE ID:
spiffe://<TRUST_DOMAIN>/workload/<WORKLOAD_NAME>
Client-side JWT retrieval model
A workload on the SPIRE Agent host should obtain its JWT from the local SPIRE agent, not from the SPIRE server directly.
Local agent socket:
<AGENT_SOCKET_PATH>
Example operational flow
- Workload on the SPIRE Agent host requests a JWT-SVID from the local SPIRE agent.
- JWT-SVID is issued with:
- issuer = https://<OIDC_DISCOVERY_DOMAIN>
- audience = <PING_AUDIENCE>
- subject = workload SPIFFE ID
- Workload presents JWT to PingFederate.
- PingFederate validates the JWT using SPIRE OIDC discovery/JWKS.
- PingFederate maps the SPIFFE workload identity to access policy, token issuance, or downstream application authorization.
Suggested placeholder legend
| Placeholder | Meaning |
|—|—|
| <SPIRE_SERVER_HOST> | SPIRE server hostname |
| <SPIRE_AGENT_HOST> | SPIRE agent hostname |
| <TRUST_DOMAIN> | SPIRE trust domain |
| <SERVER_PORT> | SPIRE server listener port |
| <HEALTH_PORT> | Health check port |
| <AGENT_SOCKET_PATH> | Local SPIRE Agent workload API socket |
| <AGENT_HASH> | x509pop parent/agent hash |
| <WORKLOAD_NAME> | Example X.509 workload name |
| <JWT_WORKLOAD_NAME> | Example JWT workload name |
| <JWT_AUDIENCE> | JWT audience used by client |
| <PING_AUDIENCE> | Audience PingFederate validates |
| <OIDC_DISCOVERY_DOMAIN> | Public/abstracted OIDC issuer hostname |
| <SPIRE_SERVER_API_SOCKET> | SPIRE server private API socket |
Possibly Edible Mushrooms
We’ve got fiber!
Flatpak – Escaping the Sandbox
We’ve been running Cura from a flatpak because the rpm distributed version was out of date. The big drawback, though, is that this flatpak could not see the files from network mounts. The mount is fine – in fstab, same user account can interact with those files in other applications. Just not this flatpak thing.
Turns out that’s normal – flatpaks operate in a sort of sandbox. You just have to tell it to let an individual flatpak access the location where the network mountsĀ are. In this case, the /mnt path:
flatpack override com.ultimaker.cura --filesystem=/mnt
Extracting Base 64 Encoded Certificate Files with OpenSSL CLI
Quick command to get the base-64 encoded certificate — useful for establishing trust with AD DS domain controllers
openssl s_client -connect dc5.example.com:636 -showcerts </dev/null 2>/dev/null | awk ‘BEGIN{c=0} /BEGIN CERTIFICATE/{c++} c==1{print} /END CERTIFICATE/ && c==1{exit}’
MiniPlasma Mitigation
Microsoft Windows Cloud Files Mini Filter Driver Elevation of Privilege Vulnerability (alternately referred to as MiniPlasma) is one of those findings that is a little confusing. The vulnerability appears tied to an issue Microsoft originally addressed in 2020, which raises the obvious question: if it was already patched, why is it relevant again now?
The original fix may have closed a specific exploitation path without fully eliminating the underlying bug class. The remediation may have been incomplete. Or a later code change may have reintroduced a previously fixed condition. Without updated vendor detail, it is hard to say exactly which of those happened; but the operational conclusion is the same: a system can still be exposed today even if the original 2020 patch was installed.
For environments looking for a practical mitigation, the good news is that many servers do not need Windows Cloud Files functionality at all. The vulnerable component is the Cloud Files Mini Filter Driver (CldFlt), which supports placeholder and hydration behavior used by features such as OneDrive Files On-Demand and other CfAPI-based integrations.
That makes CldFlt a viable mitigation target. If the filter is loaded but not attached to any volumes, there is a good chance it can be safely unloaded and disabled. This does not remove the driver from disk, but it does remove the active kernel attack surface associated with the running minifilter. Since this is an elevation-of-privilege issue, that distinction matters: the goal is not to claim the file no longer exists, but to prevent the vulnerable driver from being active in the system.
The following process checks whether any volumes are associated with CldFlt, temporarily unloads the filter, verifies that the system continues functioning normally, and then disables the driver persistently.
REM Check instances on CldFlt — if 0, proceed with testing disablement
fltmc filters
REM Example output:
REM Filter Name Num Instances Altitude Frame
REM —————————— ————- ———— —–
REM bindflt 0 409800 0
REM MsSecFlt 9 385600 0
REM CSAgent 9 321410.78870 0
REM storqosflt 0 244000 0
REM wcifs 0 189900 0
REM CldFlt 0 180451 0
REM FileCrypt 0 141100 0
REM UnionFS 0 130850 0
REM npsvctrig 1 46000 0
REM Wof 1 40700 0
REM Unload cldflt
fltmc unload cldflt
REM Verify stopped
sc query cldflt
REM Verify no longer in filters list
fltmc filters
REM Verify applications and expected file operations still work normally
REM If everything looks good, disable persistent startup
sc config cldflt start= disabled
Fedora 43 to 44 upgrade: dnf5 plugin load failure after upgrade
After completing an in-place upgrade from Fedora 43 to Fedora 44, dnf5 failed to run with this error:
[lisa@fedora05 ~]# dnf5 update
Cannot load dnf5 plugin: /usr/lib64/dnf5/plugins/automatic_cmd_plugin.so
Cannot load shared library “/usr/lib64/dnf5/plugins/automatic_cmd_plugin.so”: libdnf5-cli.so.2: cannot open shared object file: No such file or directory
What happened
The Fedora 44 upgrade completed, and the installed dnf5 packages were all current Fedora 44 versions. However, there was a leftover plugin file still sitting in /usr/lib64/dnf5/plugins/automatic_cmd_plugin.so.
That file was not owned by any RPM package and had been built against an older library, libdnf5-cli.so.2.
But Fedora 44 had /usr/lib64/libdnf5-cli.so.3.
dnf5 was trying to load a stale plugin from before the upgrade.
How I verified it
These commands showed the problem:
ls -l /usr/lib64/libdnf5-cli.so*
rpm -qf /usr/lib64/dnf5/plugins/automatic_cmd_plugin.so
ldd /usr/lib64/dnf5/plugins/automatic_cmd_plugin.so
Results:
libdnf5-cli.so.3existedautomatic_cmd_plugin.sowas not owned by any packagelddshowed it was looking forlibdnf5-cli.so.2
Fix
Remove the orphaned plugin file:
rm /usr/lib64/dnf5/plugins/automatic_cmd_plugin.so
ldconfig
dnf5 update
After deleting the stale plugin, dnf5 worked normally again.
Root cause
This appears to be a leftover orphaned dnf5 plugin from before the major version upgrade. Even though the main dnf5 and libdnf5 packages were updated correctly, dnf5 still tried to load the old .so file it found in the plugins directory.
Kafbat – OAUTH With RBAC
I encountered a challenge with a Kafka management tool — it supports SSO, and I was able to get an OAUTH connection set up to control what users could see when logging in through the UI, but the API component didn’t extract information from the bearer token and there was nothing in the rbac mapping to allow the bearer-token client ID to access anything.
Updates to allow the /api components to be authenticated by simple bearer tokens and a client ID mapped into a role are at https://github.com/ljr55555/kafka-ui
AuthorizationController.java was updated to properly support non-browser, machine-principal auth.
Added/fixed:
- avoids null failure when
authentication.getName()is missing - resolves principal name from alternate attributes such as:
client_idsubusername
- updated displayed permissions logic so
/api/authorizationuses the same effective RBAC matching idea as the backend
Result
/api/authorization now works for bearer-token API callers and shows:
- username = client ID
- populated permissions list
AccessControlService.java
Added support for API bearer-token principals
Previously, getUser() only worked when the authenticated principal was a RbacUser, which covered the browser/user flow.
Now it can also derive an AuthenticatedUser from opaque-token authenticated principals by extracting:
- principal name
- group-like values from attributes/authorities if present
Updated role matching logic
Previously, role matching was only role name matches one of user.groups(). Now it also supports role name matches user.principal(). That enables RBAC binding directly to the API client ID.
Result
RBAC now works for:
- normal browser users via groups
- API bearer-token callers via client principal name
DynamicConfigMapper.java
Fixed a mapper bug.
Before
The method mapping resource server config created a populated OAuth2ResourceServerProperties result object but always returned null.
After
It now returns result.
Result
Dynamic/config mapping for resource-server settings no longer silently discards the mapped object.
Build/package note
To preserve the browser UI, the jar needs to be built with frontend included — which you know if you read the doc … or you take my route, start it all up, test the API successfully, and then get baffled that the user UI throws
bash./gradlew clean assemble -Pinclude-frontend=true
application.yml
server:
port: 8443
ssl:
enabled: true
key-store: file:/etc/kafkaui/certs/kafbat.rushworth.us.p12
key-store-password: ${KEYSTORE_PASSWORD}
key-store-type: PKCS12
key-alias: kafbat
auth:
type: OAUTH2
oauth2:
client:
pingfed:
client-id: ${OAUTH_CLIENT_ID}
client-secret: ${OAUTH_CLIENT_SECRET}
scope:
- openid
- profile
- email
client-name: oauthclient
provider: oauthclient
redirect-uri: https://kafbat.rushworth.us:8443/login/oauth2/code/oauthclient
authorization-grant-type: authorization_code
issuer-uri: https://login.example.com
jwk-set-uri: https://login.example.com/pf/JWKS
authorization-uri: https://login.example.com/as/authorization.oauth2
token-uri: https://login.example.com/as/token.oauth2
user-info-uri: https://login.example.com/idp/userinfo.openid
user-name-attribute: username
custom-params:
type: oauth
roles-field: memberOf
resource-server:
opaque-token:
client-id: ${OAUTH_CLIENT_ID}
client-secret: ${OAUTH_CLIENT_SECRET}
introspection-uri: https://login.example.com/as/introspect.oauth2
kafka:
clusters:
- name: test
bootstrapServers: ${KAFKA_BOOTSTRAP_SERVERS}
roles.yml
rbac:
roles:
- name: "admins"
clusters:
- test
subjects:
- provider: oauth
type: role
value: "CN=KafbatAdmins,OU=SecurityGroups,DC=example,DC=com"
permissions:
- resource: applicationconfig
actions: all
- resource: clusterconfig
actions: all
- resource: topic
value: ".*"
actions: all
- resource: consumer
value: ".*"
actions: all
- resource: schema
value: ".*"
actions: all
- resource: connect
value: ".*"
actions: all
- resource: ksql
actions: all
- resource: acl
actions: [ view ]
- name: "${OAUTH_CLIENT_ID}"
clusters:
- test
subjects:
- provider: oauth
type: user
value: "${OAUTH_CLIENT_ID}"
permissions:
- resource: applicationconfig
actions: all
- resource: clusterconfig
actions: all
- resource: topic
value: ".*"
actions: all
- resource: consumer
value: ".*"
actions: all
- resource: schema
value: ".*"
- resource: connect
value: ".*"
actions: all
- resource: ksql
actions: all
- resource: acl
actions: [ view ]
docker-compose.yml
services:
redpanda:
image: redpandadata/redpanda:v25.1.2
container_name: redpanda
command:
- redpanda
- start
- --overprovisioned
- --smp=1
- --memory=1G
- --reserve-memory=0M
- --node-id=0
- --check=false
- --kafka-addr=PLAINTEXT://0.0.0.0:9092
- --advertise-kafka-addr=PLAINTEXT://redpanda:9092
ports:
- "9092:9092"
kafbat-ui:
image: ghcr.io/kafbat/kafka-ui:latest
container_name: kafbat-ui
restart: unless-stopped
depends_on:
- redpanda
ports:
- "8443:8443"
volumes:
- ./config/application.yml:/etc/kafkaui/application.yml:ro
- ./config/roles.yml:/etc/kafkaui/roles.yml:ro
- ./certs:/etc/kafkaui/certs:ro
environment:
SPRING_CONFIG_LOCATION: file:/etc/kafkaui/application.yml
SPRING_CONFIG_ADDITIONAL_LOCATION: file:/etc/kafkaui/roles.yml
KEYSTORE_PASSWORD: REDACTED
OAUTH_CLIENT_ID: REDACTED
OAUTH_CLIENT_SECRET: REDACTED
KAFKA_BOOTSTRAP_SERVERS: redpanda:9092
Docker for Java Builds
Instead of flipping back and forth between java versions for various builds, you can just use a docker container for the proper Java version to run the build.
[lisa@docker kafka-ui]# docker run --rm -it --user $(id -u):$(id -g) -v "$PWD":/workspace -w /workspace eclipse-temurin:25 bash -lc './gradlew clean build'
Downloading https://services.gradle.org/distributions/gradle-9.2.0-bin.zip
............10%.............20%.............30%.............40%.............50%.............60%.............70%.............80%.............90%.............100%
Welcome to Gradle 9.2.0!
Here are the highlights of this release:
- Windows ARM support
- Improved publishing APIs
- Better guidance for dependency verification failures
For more details see https://docs.gradle.org/9.2.0/release-notes.html
Starting a Gradle Daemon (subsequent builds will be faster)
<=============> 100% CONFIGURING [1m 46s]
> Resolve dependencies of :api:detachedConfiguration273


