Skip to content

feat(core): enable zero-config Bedrock identity defaults - #90

Merged
robinbraemer merged 4 commits into
mainfrom
fm/connect-java-bedrock-identity-zero-config-defaults
Jul 30, 2026
Merged

feat(core): enable zero-config Bedrock identity defaults#90
robinbraemer merged 4 commits into
mainfrom
fm/connect-java-bedrock-identity-zero-config-defaults

Conversation

@robinbraemer

Copy link
Copy Markdown
Member

Intent

Ship Connect Java's Bedrock identity defaults so a newly installed plugin with an untouched server or proxy config works for Connect-managed Bedrock without any operator config paste. The packaged config.yml and proxy-config.yml must default metadata-url to https://watch-connect.minekube.net/.well-known/minekube-connect/bedrock-identity-keys.json, enforcement to the non-rejecting safe starting value warn, expected-issuer to minekube-connect, and expected-policy to trusted_bedrock_xuid. Document why trusting Minekube's HTTPS metadata is coherent and safe, the verifier's userinfo/fragment/redirect rejection, and how operators clear metadata-url to pin public-key/public-keys. Prove an untouched default config advertises trusted bedrock-identity and admits a valid unlinked Bedrock session, while Java-only sessions remain allowed without key fetches, warnings, or behavioral weakening. Preserve legacy Java object fallback and static-key configuration semantics by limiting the new trust values to generated install templates. Do not touch Gate or Moxy consumption, release, deploy, merge, production, or public support threads; deliver a green PR for human review.

What Changed

  • Set generated server and proxy configs to trust Minekube Bedrock identity metadata in non-rejecting warn mode with the expected issuer and policy defaults.
  • Made remote key validation session-lazy so Java-only sessions and capability registration do not fetch metadata or change admission behavior, while valid unlinked Bedrock sessions are verified.
  • Added operator documentation for the HTTPS trust boundary and static-key pinning, plus default-config coverage and superseded the stale enforcement planning docs.

Risk Assessment

✅ Low: The session-lazy metadata change and documentation now align with the stated Java-only and zero-config Bedrock requirements; no additional material source risks were found.

Testing

Fresh targeted tests passed. Packaged config defaults, capability advertisement, valid unlinked Bedrock admission, Java-only no-fetch/no-warning behavior, metadata rejection, static-key semantics, and legacy defaults were directly verified. No UI surface applies.

Evidence: Bedrock identity validation evidence
Bedrock identity default-config validation evidence
Commit: b3132f6adab02c654ac30e8e08b621255897807e

Processed server template: core/build/resources/main/config.yml
# Connect Edge signs Bedrock/Xbox identity envelopes. This plugin trusts them by default
# because it fetches Minekube's verifier key over HTTPS from the authoritative metadata URL.
# The verifier rejects metadata URLs with userinfo/fragments and never follows redirects.
# Warn mode verifies and logs but never rejects; Java-only servers have no Bedrock sessions
# to verify, so this default does not weaken or change their admission behavior.
bedrock-identity:
  # disabled: do not verify or reject
  # warn: verify when any key source is configured and log failures, but allow the session
  # require: reject Bedrock sessions with missing or invalid identity envelopes
  # Values are exact; malformed identity settings never advertise trusted identity.
  enforcement: warn
  # To pin your own Ed25519 key, clear metadata-url and set public-key/public-keys.
  # Static keys are used only when metadata-url is empty.
  # Supports raw 32-byte and X.509 encoded keys after decoding.
  public-key: ""
  # Additional Base64-encoded Ed25519 keys used only when metadata-url is empty.
  public-keys: []
  # Authoritative exact HTTPS metadata URL for Moxy verifier keys.
  # Userinfo, fragments, and redirects are rejected; static keys are not a fallback.
  metadata-url: "https://watch-connect.minekube.net/.well-known/minekube-connect/bedrock-identity-keys.json"
  # Maximum seconds to cache successful metadata; a smaller published max-age takes precedence.
  metadata-cache-seconds: 300
  # Hard stale-if-error bound for expired remote keys. Require mode fails closed after this.
  metadata-max-stale-seconds: 600
  # Required non-blank issuer expected in Moxy metadata and signed envelopes.
  expected-issuer: minekube-connect
  # Required exact policy: linked_java_only or trusted_bedrock_xuid.
  expected-policy: trusted_bedrock_xuid

# The default locale for Connect. By default, Connect uses the system locale
Processed proxy template: core/build/resources/main/proxy-config.yml
# Connect Edge signs Bedrock/Xbox identity envelopes. This plugin trusts them by default
# because it fetches Minekube's verifier key over HTTPS from the authoritative metadata URL.
# The verifier rejects metadata URLs with userinfo/fragments and never follows redirects.
# Warn mode verifies and logs but never rejects; Java-only servers have no Bedrock sessions
# to verify, so this default does not weaken or change their admission behavior.
bedrock-identity:
  # disabled: do not verify or reject
  # warn: verify when any key source is configured and log failures, but allow the session
  # require: reject Bedrock sessions with missing or invalid identity envelopes
  # Values are exact; malformed identity settings never advertise trusted identity.
  enforcement: warn
  # To pin your own Ed25519 key, clear metadata-url and set public-key/public-keys.
  # Static keys are used only when metadata-url is empty.
  # Supports raw 32-byte and X.509 encoded keys after decoding.
  public-key: ""
  # Additional Base64-encoded Ed25519 keys used only when metadata-url is empty.
  public-keys: []
  # Authoritative exact HTTPS metadata URL for Moxy verifier keys.
  # Userinfo, fragments, and redirects are rejected; static keys are not a fallback.
  metadata-url: "https://watch-connect.minekube.net/.well-known/minekube-connect/bedrock-identity-keys.json"
  # Maximum seconds to cache successful metadata; a smaller published max-age takes precedence.
  metadata-cache-seconds: 300
  # Hard stale-if-error bound for expired remote keys. Require mode fails closed after this.
  metadata-max-stale-seconds: 600
  # Required non-blank issuer expected in Moxy metadata and signed envelopes.
  expected-issuer: minekube-connect
  # Required exact policy: linked_java_only or trusted_bedrock_xuid.
  expected-policy: trusted_bedrock_xuid

# bStats is a stat tracker that is entirely anonymous and tracks only basic information
README product-facing entry point:
## Bedrock identity verification

New installations trust Connect Edge's signed Bedrock identities in non-rejecting `warn` mode
without requiring operators to paste key configuration. See
[docs/bedrock-identity.md](docs/bedrock-identity.md) for the trust boundary, Java-only behavior,
and static-key pinning.

## Connect libp2p endpoint mode
Trust-boundary documentation:
# Bedrock identity defaults

Connect Edge authenticates Bedrock players with Microsoft/Xbox and signs a short-lived,
endpoint-scoped identity before forwarding the session. A newly installed Connect Java plugin
trusts that Minekube-signed identity without extra operator configuration: it validates the
metadata URL syntax at registration and advertises `bedrock-identity-v1`. When a Bedrock session
arrives, it fetches the current Ed25519 verifier key over HTTPS from Minekube's authoritative
metadata endpoint and verifies the metadata lazily for that session.

This default is appropriate because the Minekube Connect plugin is receiving sessions from the
Minekube Connect edge. The metadata response contains public verifier keys only. The connector
requires HTTPS, rejects URLs containing userinfo or fragments, refuses redirects, and checks that
the metadata issuer is exactly `minekube-connect`.

The default enforcement mode is `warn`. It verifies Bedrock identities and logs failures, but it
never rejects a session. Java sessions without the reserved Bedrock identity property return
through the Java path without fetching identity keys, logging identity warnings, or changing the
admission decision. Operators can move to `require` only after confirming their Bedrock traffic
verifies successfully.

## Pinning another verifier key

To replace Minekube's metadata source with an operator-controlled Ed25519 key, clear
`metadata-url` and set `public-key`, or populate `public-keys` during rotation:

`` `yaml
bedrock-identity:
  enforcement: warn
  metadata-url: ""
  public-key: "<base64-encoded Ed25519 public key>"
  public-keys: []
  expected-issuer: minekube-connect
  expected-policy: trusted_bedrock_xuid
`` `

Static keys are intentionally not a fallback while `metadata-url` is configured. This keeps the
active trust source explicit.
Focused test result records:
<testsuite name="com.minekube.connect.bedrock.BedrockIdentityDefaultConfigTest" tests="4" skipped="0" failures="0" errors="0" timestamp="2026-07-30T20:52:13" hostname="Robins-MacBook-Pro.local" time="4.131">
  <testcase name="untouchedDefaultsDoNotRejectOrFetchKeysForJavaOnlySessions()" classname="com.minekube.connect.bedrock.BedrockIdentityDefaultConfigTest" time="3.399"/>
  <testcase name="untouchedDefaultsAdvertiseDuringWatchRegistrationWithoutFetchingMetadata()" classname="com.minekube.connect.bedrock.BedrockIdentityDefaultConfigTest" time="0.243"/>
  <testcase name="untouchedServerConfigAdvertisesAndAdmitsValidUnlinkedBedrock()" classname="com.minekube.connect.bedrock.BedrockIdentityDefaultConfigTest" time="0.273"/>
  <testcase name="serverAndProxyShipTrustedWarnDefaults()" classname="com.minekube.connect.bedrock.BedrockIdentityDefaultConfigTest" time="0.213"/>
<testsuite name="com.minekube.connect.bedrock.BedrockIdentityKeyProviderTest" tests="13" skipped="0" failures="0" errors="0" timestamp="2026-07-30T20:52:19" hostname="Robins-MacBook-Pro.local" time="7.224">
  <testcase name="usesBoundedStaleKeysWhenMetadataScopeRefreshIsInvalid()" classname="com.minekube.connect.bedrock.BedrockIdentityKeyProviderTest" time="0.185"/>
  <testcase name="backsOffFailedRefreshesAndFailsClosedAfterHardStaleDeadline()" classname="com.minekube.connect.bedrock.BedrockIdentityKeyProviderTest" time="0.015"/>
  <testcase name="failsClosedWhenConfiguredMetadataCannotBeInitiallyValidated()" classname="com.minekube.connect.bedrock.BedrockIdentityKeyProviderTest" time="0.008"/>
  <testcase name="limitsMetadataCallsToFiveSeconds()" classname="com.minekube.connect.bedrock.BedrockIdentityKeyProviderTest" time="6.092"/>
  <testcase name="rejectsPlaintextMetadataUrlBeforeFetching()" classname="com.minekube.connect.bedrock.Bedr

... [15586 bytes truncated] ...

"0.001"/>
  <testcase name="usesPostFailureTimeForStaleEligibilityAndRetryBackoff()" classname="com.minekube.connect.bedrock.BedrockIdentityKeyProviderTest" time="0.01"/>
  <testcase name="rejectsStaticKeyThatTheVerifierCannotParse()" classname="com.minekube.connect.bedrock.BedrockIdentityKeyProviderTest" time="0.0"/>
  <testcase name="returnsStaticConfiguredKeys()" classname="com.minekube.connect.bedrock.BedrockIdentityKeyProviderTest" time="0.001"/>
  <testcase name="fetchesCurrentAndPreviousMetadataKeys()" classname="com.minekube.connect.bedrock.BedrockIdentityKeyProviderTest" time="0.004"/>
  <testcase name="rejectsRedirectAndOversizedMetadataBeforeCaching()" classname="com.minekube.connect.bedrock.BedrockIdentityKeyProviderTest" time="0.007"/>
  <testcase name="rejectsMetadataUrlsWithRawUserinfoDelimiterBeforeFetching()" classname="com.minekube.connect.bedrock.BedrockIdentityKeyProviderTest" time="0.002"/>
  <testcase name="sharesOneExpiredMetadataRefreshAcrossConcurrentCallers()" classname="com.minekube.connect.bedrock.BedrockIdentityKeyProviderTest" time="0.273"/>
  <testcase name="rejectsMetadataWithUnexpectedIssuerOrInvalidKeySize()" classname="com.minekube.connect.bedrock.BedrockIdentityKeyProviderTest" time="0.005"/>
<testsuite name="com.minekube.connect.bedrock.BedrockIdentityReadinessTest" tests="4" skipped="0" failures="0" errors="0" timestamp="2026-07-30T20:59:26" hostname="Robins-MacBook-Pro.local" time="0.003">
  <testcase name="rejectsNormalizedModeAndBlankIssuerFromReadiness()" classname="com.minekube.connect.bedrock.BedrockIdentityReadinessTest" time="0.001"/>
  <testcase name="requiresNonDisabledEnforcementAndValidatedStaticKey()" classname="com.minekube.connect.bedrock.BedrockIdentityReadinessTest" time="0.001"/>
  <testcase name="fansReadinessTransitionsToWatchAndLibp2pIndependently()" classname="com.minekube.connect.bedrock.BedrockIdentityReadinessTest" time="0.0"/>
  <testcase name="metadataDoesNotNeedInitialFetchBeforeAdvertising()" classname="com.minekube.connect.bedrock.BedrockIdentityReadinessTest" time="0.001"/>
<testsuite name="com.minekube.connect.bedrock.BedrockIdentityConfigurationTest" tests="2" skipped="0" failures="0" errors="0" timestamp="2026-07-30T20:59:17" hostname="Robins-MacBook-Pro.local" time="0.104">
  <testcase name="requiresNonblankIssuerWithoutNormalizingIt()" classname="com.minekube.connect.bedrock.BedrockIdentityConfigurationTest" time="0.101"/>
  <testcase name="acceptsOnlyExactClosedConfigurationValues()" classname="com.minekube.connect.bedrock.BedrockIdentityConfigurationTest" time="0.001"/>
<testsuite name="com.minekube.connect.bedrock.BedrockIdentityEnforcerTest" tests="29" skipped="0" failures="0" errors="0" timestamp="2026-07-30T20:59:19" hostname="Robins-MacBook-Pro.local" time="0.704">
  <testcase name="requireModeAllowsValidLegacyUnspecifiedEnvelope()" classname="com.minekube.connect.bedrock.BedrockIdentityEnforcerTest" time="0.007"/>
  <testcase name="requireModeRejectsInvalidPublicKey()" classname="com.minekube.connect.bedrock.BedrockIdentityEnforcerTest" time="0.001"/>
  <testcase name="slowMetadataVerificationDoesNotBlockUnrelatedStageOrRemove()" classname="com.minekube.connect.bedrock.BedrockIdentityEnforcerTest" time="0.174"/>
  <testcase name="requireModeRejectsOrgScopeMismatchWhenLocalScopeIsPresent()" classname="com.minekube.connect.bedrock.BedrockIdentityEnforcerTest" time="0.032"/>
  <testcase name="requireModeRejectsMissingIdentity()" classname="com.minekube.connect.bedrock.BedrockIdentityEnforcerTest" time="0.001"/>
  <testcase name="replacementDuringSlowMetadataVerificationCannotPublishStaleClaims()" classname="com.minekube.connect.bedrock.BedrockIdentityEnforcerTest" time="0.01"/>
  <testcase name="requireModeRejectsIdentityWhenEitherAuthenticatedScopeValueIsMissing()" classname="com.minekube.connect.bedrock.BedrockIdentityEnforcerTest" time="0.005"/>
  <testcase name="warnFailedAdmissionDropsEnvelopeWithoutPublishingClaims()" classname="com.minekube.connect.bedrock.BedrockIdentityEnforcerTest" time="0.002"/>
  <testcase name="requireModeAllowsEnvelopeSignedByAdditionalPublicKey()" classname="com.minekube.connect.bedrock.BedrockIdentityEnforcerTest" time="0.005"/>
  <testcase name="disabledModeAllowsMissingIdentityWithoutLogging()" classname="com.minekube.connect.bedrock.BedrockIdentityEnforcerTest" time="0.002"/>
  <testcase name="enforcerDoesNotOwnACompatibilityClaimsRegistry()" classname="com.minekube.connect.bedrock.BedrockIdentityEnforcerTest" time="0.001"/>
  <testcase name="disabledAdmissionDropsEnvelopeWithoutPublishingClaims()" classname="com.minekube.connect.bedrock.BedrockIdentityEnforcerTest" time="0.001"/>
  <testcase name="warnModeNeverPublishesClaimsForUnrecognizedProtocol()" classname="com.minekube.connect.bedrock.BedrockIdentityEnforcerTest" time="0.003"/>
  <testcase name="requireModeRejectsReservedIdentityOnPassthroughJavaAdmission()" classname="com.minekube.connect.bedrock.BedrockIdentityEnforcerTest" time="0.001"/>
  <testcase name="warnModeAllowsInvalidPublicKeyWithoutLoggingKeyMaterial()" classname="com.minekube.connect.bedrock.BedrockIdentityEnforcerTest" time="0.001"/>
  <testcase name="slowMetadataVerificationDoesNotBlockExpiryAndCannotPublishAfterIt()" classname="com.minekube.connect.bedrock.BedrockIdentityEnforcerTest" time="0.303"/>
  <testcase name="retainsLegacyConfigConstructor()" classname="com.minekube.connect.bedrock.BedrockIdentityEnforcerTest" time="0.004"/>
  <testcase name="rejectedAdmissionDropsEnvelopeWithoutPublishingClaims()" classname="com.minekube.connect.bedrock.BedrockIdentityEnforcerTest" time="0.004"/>
  <testcase name="requireModeRejectsEndpointScopeMismatchWhenLocalScopeIsPresent()" classname="com.minekube.connect.bedrock.BedrockIdentityEnforcerTest" time="0.005"/>
  <testcase name="requireModeRejectsMarkedBedrockWithoutReservedIdentity()" classname="com.minekube.connect.bedrock.BedrockIdentityEnforcerTest" time="0.001"/>
  <testcase name="slowMetadataVerificationDoesNotBlockCloseAndCannotPublishAfterIt()" classname="com.minekube.connect.bedrock.BedrockIdentityEnforcerTest" time="0.007"/>
  <testcase name="requireModeLeavesMarkedJavaWithoutReservedIdentityUnchanged()" classname="com.minekube.connect.bedrock.BedrockIdentityEnforcerTest" time="0.001"/>
  <testcase name="requireModeRejectsReplay()" classname="com.minekube.connect.bedrock.BedrockIdentityEnforcerTest" time="0.005"/>
  <testcase name="retainsLegacyHttpClientConstructor()" classname="com.minekube.connect.bedrock.BedrockIdentityEnforcerTest" time="0.002"/>
  <testcase name="legacyConstructorUsesContextAdmissionRegistry()" classname="com.minekube.connect.bedrock.BedrockIdentityEnforcerTest" time="0.005"/>
  <testcase name="malformedConfigurationRejectsOtherwiseValidIdentity()" classname="com.minekube.connect.bedrock.BedrockIdentityEnforcerTest" time="0.003"/>
  <testcase name="requireModeAllowsValidEnvelopeAndReturnsClaims()" classname="com.minekube.connect.bedrock.BedrockIdentityEnforcerTest" time="0.016"/>
  <testcase name="warnModeLogsAndAllowsMissingIdentity()" classname="com.minekube.connect.bedrock.BedrockIdentityEnforcerTest" time="0.053"/>
  <testcase name="admissionPublishesClaimsWithoutExposingOrRetainingEnvelope()" classname="com.minekube.connect.bedrock.BedrockIdentityEnforcerTest" time="0.033"/>
<testsuite name="com.minekube.connect.config.ConfigLoaderTest" tests="5" skipped="0" failures="0" errors="0" timestamp="2026-07-30T20:59:26" hostname="Robins-MacBook-Pro.local" time="1.266">
  <testcase name="shippedProxyTemplateDocumentsTheFullProfilePrerequisite()" classname="com.minekube.connect.config.ConfigLoaderTest" time="0.001"/>
  <testcase name="loadsLoginReassertOverrides()" classname="com.minekube.connect.config.ConfigLoaderTest" time="0.59"/>
  <testcase name="loadsDocumentedAllowOfflineModePlayersKey()" classname="com.minekube.connect.config.ConfigLoaderTest" time="0.222"/>
  <testcase name="loginReassertDefaultsToOnAndPropertiesOnly()" classname="com.minekube.connect.config.ConfigLoaderTest" time="0.212"/>
  <testcase name="loadsBedrockIdentityEnforcementConfig()" classname="com.minekube.connect.config.ConfigLoaderTest" time="0.231"/>

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

🔧 **Review** - 1 issue found → auto-fixed (2) ✅
  • 🚨 core/src/main/resources/config.yml:37 - The changed template enables enforcement: warn and sets metadata-url to the Minekube endpoint (line 37), so every WatchClient.watch()/libp2p registration checks readiness and synchronously fetches keys before any player is known. This conflicts with the required criterion “Java-only sessions remain allowed without key fetches.” The new test only calls the enforcer directly and misses this registration path. Please clarify whether startup capability discovery fetches are exempt; otherwise move key acquisition out of readiness negotiation.

🔧 Fix: Made Bedrock metadata fetch session-lazy; focused tests pass
1 warning still open:

  • ⚠️ docs/bedrock-identity.md:7 - The documentation says the capability is advertised “after validating the metadata,” but hasConfiguredKeySource() now only validates the URL syntax; metadata is fetched and validated later during session verification. Update the wording to describe this session-lazy flow.

🔧 Fix: Aligned Bedrock identity docs with lazy metadata validation
✅ Re-checked - no issues remain.

✅ **Test** - passed

✅ No issues found.

  • ./gradlew :core:test --rerun-tasks --tests com.minekube.connect.bedrock.BedrockIdentityDefaultConfigTest --tests com.minekube.connect.bedrock.BedrockIdentityKeyProviderTest --tests com.minekube.connect.bedrock.BedrockIdentityReadinessTest --tests com.minekube.connect.bedrock.BedrockIdentityConfigurationTest --tests com.minekube.connect.bedrock.BedrockIdentityEnforcerTest --tests com.minekube.connect.config.ConfigLoaderTest
  • Inspected packaged JAR templates, documentation, and legacy object defaults.
  • ./gradlew clean and generated-output cleanup
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

@robinbraemer
robinbraemer merged commit d381f42 into main Jul 30, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant