Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,12 @@ jobs:
secret="${mapping#*:}"

if ! awk -v property="$property" -v secret="$secret" '
BEGIN { empty = sprintf("%c%c", 39, 39) }
$1 == property ":" {
if ($2 == ("$" "{{") && $3 == "secrets." secret && $4 == "}}" && NF == 4) {
if ($2 == ("$" "{{") && $3 == "vars.MAVEN_CENTRAL_AUTH_PROXY_URL" &&
$4 == "==" && $5 == empty && $6 == "&&" &&
$7 == "secrets." secret && $8 == "||" && $9 == empty &&
$10 == "}}" && NF == 10) {
valid++
} else {
invalid = 1
Expand Down
37 changes: 29 additions & 8 deletions .github/workflows/create-releases.yml
Original file line number Diff line number Diff line change
Expand Up @@ -434,18 +434,19 @@ jobs:

- name: Publish to Maven Central
env:
ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.OPENAI_SONATYPE_USERNAME }}
ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.OPENAI_SONATYPE_PASSWORD }}
MAVEN_CENTRAL_AUTH_PROXY_URL: ${{ vars.MAVEN_CENTRAL_AUTH_PROXY_URL }}
ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ vars.MAVEN_CENTRAL_AUTH_PROXY_URL == '' && secrets.OPENAI_SONATYPE_USERNAME || '' }}
ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ vars.MAVEN_CENTRAL_AUTH_PROXY_URL == '' && secrets.OPENAI_SONATYPE_PASSWORD || '' }}
GPG_SIGNING_KEY: ${{ secrets.OPENAI_SONATYPE_GPG_SIGNING_KEY }}
GPG_SIGNING_PASSWORD: ${{ secrets.OPENAI_SONATYPE_GPG_SIGNING_PASSWORD }}
run: |
set -euo pipefail

for variable in \
ORG_GRADLE_PROJECT_mavenCentralUsername \
ORG_GRADLE_PROJECT_mavenCentralPassword \
GPG_SIGNING_KEY \
GPG_SIGNING_PASSWORD; do
required_variables=(GPG_SIGNING_KEY GPG_SIGNING_PASSWORD)
if [[ -z "$MAVEN_CENTRAL_AUTH_PROXY_URL" ]]; then
required_variables+=(ORG_GRADLE_PROJECT_mavenCentralUsername ORG_GRADLE_PROJECT_mavenCentralPassword)
fi
for variable in "${required_variables[@]}"; do
if [[ -z "${!variable}" ]]; then
echo "::error::$variable is empty"
exit 1
Expand All @@ -470,13 +471,33 @@ jobs:
publish_exclusions+=("--exclude-task" ":$artifact:jar")
done

publish_tasks=(publishAndReleaseToMavenCentral)
if [[ -n "${MAVEN_CENTRAL_AUTH_PROXY_URL:-}" ]]; then
if [[ -e build/auth-proxy-staging || -L build/auth-proxy-staging ]]; then
echo "::error::Proxy staging directory must be fresh"
exit 1
fi
publish_tasks=(publishAllPublicationsToAuthProxyStagingRepository -PstageForAuthProxy=true)
fi

sha256sum --check "$RUNNER_TEMP/maven-artifact-provenance.sha256"
./gradlew publishAndReleaseToMavenCentral \
./gradlew "${publish_tasks[@]}" \
"${publish_exclusions[@]}" \
--stacktrace \
--no-parallel \
--no-configuration-cache

- name: Publish through Maven Central auth proxy
if: vars.MAVEN_CENTRAL_AUTH_PROXY_URL != ''
env:
JAVA_TOOL_OPTIONS: -Djdk.httpclient.redirects.retrylimit=1
MAVEN_CENTRAL_AUTH_PROXY_URL: ${{ vars.MAVEN_CENTRAL_AUTH_PROXY_URL }}
MAVEN_CENTRAL_AZURE_TENANT_ID: ${{ vars.MAVEN_CENTRAL_AZURE_TENANT_ID }}
MAVEN_CENTRAL_AZURE_CLIENT_ID: ${{ vars.MAVEN_CENTRAL_AZURE_CLIENT_ID }}
MAVEN_CENTRAL_AZURE_RESOURCE: ${{ vars.MAVEN_CENTRAL_AZURE_RESOURCE }}
RELEASE_TAG: ${{ needs.release.outputs.release_tag }}
run: ./gradlew publishViaAuthProxy -PstageForAuthProxy=true --no-daemon --no-configuration-cache

- name: Verify attested Maven artifacts
run: sha256sum --check "$RUNNER_TEMP/maven-artifact-provenance.sha256"

Expand Down
35 changes: 35 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,41 @@ these commands with shell tracing enabled.
After the rotated secrets work, revoke the old Central Portal token and remove any old repository-level copies of the
`OPENAI_SONATYPE_*` secrets.

### Publish through an authentication proxy

The release workflow can upload through an HTTPS credential proxy instead of exposing the Central
Portal token to the runner. Leave `MAVEN_CENTRAL_AUTH_PROXY_URL` unset to keep direct publishing.
After the proxy owner has reviewed and provisioned the integration, configure these variables in
the protected `publish` environment:

- `MAVEN_CENTRAL_AUTH_PROXY_URL`: the proxy's HTTPS origin, without a path or query.
- `MAVEN_CENTRAL_AZURE_TENANT_ID`: the Microsoft Entra tenant UUID.
- `MAVEN_CENTRAL_AZURE_CLIENT_ID`: the dedicated federated application UUID.
- `MAVEN_CENTRAL_AZURE_RESOURCE`: the proxy's Entra resource identifier, without `/.default`.

The application must trust GitHub OIDC for this repository's `publish` environment. Preserve its
`main`-only deployment restriction: the default environment subject does not also constrain the
branch or workflow. Review stronger workflow-specific federation with the proxy owner. The proxy
must validate the Entra application identity and inject the Central Portal bearer credential only
for its fixed Sonatype destination. Grant only upload, status and publish operations. Do not inspect
the ZIP body or assume a generic proxy's upload limits/timeouts fit a full SDK release.

Before enabling the variable, complete an owner-approved, nonpublishing `USER_MANAGED` canary and
verify the real bundle size, token exchange, signatures and gateway behavior. The enabled release
workflow **does publish automatically after validation**. It stages signed artifacts without vendor
credentials, derives the expected inventory from Gradle Maven publications, verifies signatures and attested JAR hashes, then uploads once and records the
bundle digest and deployment ID in the job log and summary. The uploader is the `publishViaAuthProxy` Kotlin Gradle task in `buildSrc`; it uses the build JDK.
The workflow sets `JAVA_TOOL_OPTIONS=-Djdk.httpclient.redirects.retrylimit=1` before starting
that JVM to disable HTTP-client retries as well as application-level retries.

Failures never fall back to direct publishing or retry an upload automatically. An upload timeout
can mean Central accepted the bundle: inspect Portal before retrying, even if no deployment ID was
returned. For a known ID, inspect that deployment rather than starting another upload. Proxy mode
requires a release source containing the signed-staging Gradle support; older release retries must
use the direct path after confirming no existing deployment. Keep GPG signing secrets configured.
Remove/revoke the GitHub Sonatype token only after a successful approved rollout. Once it is revoked,
clearing the proxy URL alone is not sufficient to restore direct publishing.

### Publish manually

The GitHub workflow is preferred because it validates the immutable release identity and requires a Central Portal check
Expand Down
7 changes: 7 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import com.openai.gradle.MavenCentralProxyPublisher
import com.openai.gradle.CoreCompilationShards
import com.openai.gradle.GenerateVersionSupportMatrixTask
import com.openai.gradle.VersionSupportPolicy
Expand Down Expand Up @@ -184,3 +185,9 @@ tasks.named("dokkaJavadocCollector").configure {
}
.forEach { mustRunAfter(it) }
}

// Run separately after signed staging so no Sonatype credentials enter this task.
tasks.register("publishViaAuthProxy") {
notCompatibleWithConfigurationCache("Release credentials are read only during execution")
doLast { MavenCentralProxyPublisher.run(rootProject) }
}
Loading
Loading