From dbcdd9657a21a0728035439a726223506a82da62 Mon Sep 17 00:00:00 2001 From: Zulu Date: Sat, 5 Sep 2026 01:42:39 +0100 Subject: [PATCH] Simplify provider registration authorization --- docs/nethernet-provider-registration-v0.md | 37 ++++++++---------- .../netty/warden/ProviderClient.java | 15 +++---- .../netty/warden/ProviderStateStore.java | 2 +- .../provider-registration-v0.schema.json | 39 ++++++------------- .../warden/admission/ProviderNativeBench.java | 2 +- .../netty/warden/IndependentProviderStub.java | 8 ++-- .../netty/warden/ProviderClientTest.java | 14 +++---- .../netty/warden/WardenProviderBench.java | 7 ++-- .../provider-registration-v0.fixtures.json | 26 ++++++------- .../provider-registration-v0.schema.json | 39 ++++++------------- 10 files changed, 73 insertions(+), 116 deletions(-) diff --git a/docs/nethernet-provider-registration-v0.md b/docs/nethernet-provider-registration-v0.md index baaaf287..1b7f577c 100644 --- a/docs/nethernet-provider-registration-v0.md +++ b/docs/nethernet-provider-registration-v0.md @@ -24,12 +24,11 @@ Registration combines one mode with one advertised authorization scheme: | Standalone server, no credential | `new-service` | `anonymous-proof-of-work` | New public service and machine identity | | Existing provider customer | `new-service` | `bearer-token` | Account-owned service with no PoW | | Autoscaled proxy replica | `attach-instance` | `bearer-token` | Machine in an existing service/pool with no PoW | -| One-machine controller handoff | `attach-instance` | `bootstrap-grant` | Attachment using a short-lived key-bound grant | | Server host's own infrastructure | Either | Any advertised scheme | Registration stays at the configured host origin | -Provider policy decides what a token represents. It may map to an account that -can create services, or to a fixed service, region, pool, and tag set. That mapping -is deliberately outside the protocol. +Provider policy decides whether a token is reusable, short-lived, single-use or +key-bound, and whether it can create services or attach to a fixed placement. +Token issuance and exchange are outside this protocol. ## Discovery and trust @@ -44,8 +43,7 @@ profiles, operations, authorization, policy, and limits. For example: "header": "Authorization", "schemes": [ { "scheme": "anonymous-proof-of-work", "modes": ["new-service"] }, - { "scheme": "bearer-token", "modes": ["new-service", "attach-instance"] }, - { "scheme": "bootstrap-grant", "modes": ["attach-instance"] } + { "scheme": "bearer-token", "modes": ["new-service", "attach-instance"] } ] } } @@ -62,7 +60,7 @@ responses, proof payloads, durable machine state, or logs. ## Challenge request -First create and durably store a fresh P-384 machine key. A fleet request is: +First create and durably store a fresh P-384 instance key. A fleet request is: ```json { @@ -81,19 +79,18 @@ First create and durably store a fresh P-384 machine key. A fleet request is: ``` `new-service` cannot name a service or instance. `attach-instance` requires an -explicit placement and provider-resolved authority for the existing service. A -`bootstrap-grant` request also carries `bootstrapGrant` in JSON; the grant should -be short-lived and bound to the machine public-key thumbprint. - -For first-v0 compatibility, omitting `authorization` implies `bootstrap-grant` -when `bootstrapGrant` is present, otherwise `anonymous-proof-of-work`. New clients -should send the selection explicitly. +explicit placement and a bearer token authorizing the existing service. Clients +send the authorization selection explicitly. Placement contains `region`, `pool`, and at most 16 provider-defined tags. Tag keys match `[A-Za-z0-9_.-]{1,32}`. Values are trimmed, non-empty strings of at most 64 characters. Tags are sorted by key for canonicalization. Providers authorize the exact placement; labels do not grant authority. +An instance key identifies one logical instance, not a node or fleet. Concurrent +instances must not share or copy a key or state directory. A restart of the same +logical instance reuses its state; images and templates must not contain it. + ## Proof of work and possession The provider stores and returns a challenge containing the machine thumbprint, @@ -110,7 +107,7 @@ integer epoch milliseconds, and empty strings for absent context strings. The ba context digest hashes: ```text -[mode, profile, label, grantId, serviceId, region, pool, registrationId] +[mode, profile, label, authorizationId, serviceId, region, pool, registrationId] ``` When tags are non-empty, append `tagsDigest`: unpadded base64url SHA-256 of the @@ -128,10 +125,10 @@ ES384 signatures are 96-byte P1363 `r || s`, unpadded base64url; DER is rejected PoW counts leading zero bits of SHA-256 over the same proof bytes. Completion sends `protocol`, `challengeId`, `proofNonce`, `idempotencyKey`, and `signature`. -The provider revalidates token or grant authority at atomic completion. A token -revoked after challenge creation fails closed. Challenge/grant consumption and all -created resources commit together. Completion is single-use and must not replay -one-time secrets. +The provider revalidates token authority at atomic completion. A token revoked +after challenge creation fails closed. Authority validation and all created +resources commit together. Completion is single-use and must not replay one-time +secrets. ## Result and lifecycle @@ -141,7 +138,7 @@ return one-time ticket material needed by the profile. Anonymous creation may return an optional provider-account claim action. Token-owned creation should not require a second claim. -Persist the private key before requesting a challenge, the challenge before +Persist the instance private key before requesting a challenge, the challenge before completion, and returned IDs/key material before activation. On restart, use the discovered recovery operation and prove possession of the same key. Do not register a new machine merely because a process restarted. diff --git a/warden-signalling/src/main/java/org/cloudburstmc/netty/warden/ProviderClient.java b/warden-signalling/src/main/java/org/cloudburstmc/netty/warden/ProviderClient.java index 849de221..4f8d68a4 100644 --- a/warden-signalling/src/main/java/org/cloudburstmc/netty/warden/ProviderClient.java +++ b/warden-signalling/src/main/java/org/cloudburstmc/netty/warden/ProviderClient.java @@ -14,26 +14,22 @@ /** One asynchronous, serialized control lifecycle per backend, never one poller per player. */ public final class ProviderClient implements AutoCloseable { public static final String NEW_SERVICE = "new-service", ATTACH_INSTANCE = "attach-instance"; - public static final String ANONYMOUS_PROOF_OF_WORK = "anonymous-proof-of-work", BEARER_TOKEN = "bearer-token", BOOTSTRAP_GRANT = "bootstrap-grant"; + public static final String ANONYMOUS_PROOF_OF_WORK = "anonymous-proof-of-work", BEARER_TOKEN = "bearer-token"; public record Configuration(URI provider, String profile, String label, String registrationMode, String authorizationScheme, - String authorizationToken, String bootstrapGrant, String region, String pool, Map tags) { + String authorizationToken, String region, String pool, Map tags) { public Configuration { Objects.requireNonNull(provider); Objects.requireNonNull(profile); Objects.requireNonNull(registrationMode); Objects.requireNonNull(authorizationScheme); tags = tags == null ? Map.of() : Collections.unmodifiableMap(new TreeMap<>(tags)); if (!Set.of(NEW_SERVICE, ATTACH_INSTANCE).contains(registrationMode)) throw new IllegalArgumentException("Invalid provider registration mode"); - if (!Set.of(ANONYMOUS_PROOF_OF_WORK, BEARER_TOKEN, BOOTSTRAP_GRANT).contains(authorizationScheme)) throw new IllegalArgumentException("Invalid provider authorization scheme"); + if (!Set.of(ANONYMOUS_PROOF_OF_WORK, BEARER_TOKEN).contains(authorizationScheme)) throw new IllegalArgumentException("Invalid provider authorization scheme"); if ((BEARER_TOKEN.equals(authorizationScheme)) != (authorizationToken != null && !authorizationToken.isBlank())) throw new IllegalArgumentException("Bearer authorization requires exactly one token"); - if ((BOOTSTRAP_GRANT.equals(authorizationScheme)) != (bootstrapGrant != null && !bootstrapGrant.isBlank())) throw new IllegalArgumentException("Bootstrap authorization requires exactly one grant"); if (ANONYMOUS_PROOF_OF_WORK.equals(authorizationScheme) && !NEW_SERVICE.equals(registrationMode)) throw new IllegalArgumentException("Anonymous proof of work can only create a service"); - if (BOOTSTRAP_GRANT.equals(authorizationScheme) && !ATTACH_INSTANCE.equals(registrationMode)) throw new IllegalArgumentException("Bootstrap grants can only attach an instance"); if (ATTACH_INSTANCE.equals(registrationMode) && (region == null || region.isBlank() || pool == null || pool.isBlank())) throw new IllegalArgumentException("Attached instances require region and pool"); if ((region == null) != (pool == null) || (!tags.isEmpty() && region == null)) throw new IllegalArgumentException("Provider placement requires region and pool together"); if (tags.size() > 16 || tags.entrySet().stream().anyMatch(e -> !e.getKey().matches("[A-Za-z0-9_.-]{1,32}") || e.getValue() == null || !e.getValue().equals(e.getValue().trim()) || e.getValue().isEmpty() || e.getValue().length() > 64)) throw new IllegalArgumentException("Invalid provider placement tags"); } - /** Compatibility constructor for the original anonymous/grant configuration. */ - public Configuration(URI provider, String profile, String label, String bootstrapGrant, String region, String pool) { - this(provider, profile, label, bootstrapGrant == null ? NEW_SERVICE : ATTACH_INSTANCE, - bootstrapGrant == null ? ANONYMOUS_PROOF_OF_WORK : BOOTSTRAP_GRANT, null, bootstrapGrant, region, pool, Map.of()); + public Configuration(URI provider, String profile, String label) { + this(provider, profile, label, NEW_SERVICE, ANONYMOUS_PROOF_OF_WORK, null, null, null, Map.of()); } @Override public String toString() { return "Configuration[provider=" + provider + ", profile=" + profile + ", registrationMode=" + registrationMode + ", authorizationScheme=" + authorizationScheme + "]"; } } @@ -165,7 +161,6 @@ private void enroll() throws Exception { JsonObject request = new JsonObject(); request.addProperty("protocol", ProviderCrypto.PROTOCOL); request.addProperty("mode", config.registrationMode()); request.addProperty("profile", config.profile()); request.add("publicKeyJwk", state.get("publicKeyJwk")); if (config.label() != null) request.addProperty("label", config.label()); JsonObject authorization = new JsonObject(); authorization.addProperty("scheme", config.authorizationScheme()); request.add("authorization", authorization); - if (config.bootstrapGrant() != null) request.addProperty("bootstrapGrant", config.bootstrapGrant()); if (config.region() != null) { JsonObject p = new JsonObject(); p.addProperty("region", config.region()); p.addProperty("pool", config.pool()); if (!config.tags().isEmpty()) p.add("tags", JSON.toJsonTree(config.tags())); request.add("placement", p); } challenge = unsigned("challenges", request, config.authorizationToken()); state.add("challenge", challenge); save(); } diff --git a/warden-signalling/src/main/java/org/cloudburstmc/netty/warden/ProviderStateStore.java b/warden-signalling/src/main/java/org/cloudburstmc/netty/warden/ProviderStateStore.java index f855567a..8d6be6dd 100644 --- a/warden-signalling/src/main/java/org/cloudburstmc/netty/warden/ProviderStateStore.java +++ b/warden-signalling/src/main/java/org/cloudburstmc/netty/warden/ProviderStateStore.java @@ -7,7 +7,7 @@ import java.nio.file.*; import java.nio.file.attribute.PosixFilePermissions; -/** One process owns a directory; atomic file replacement precedes any readiness advertisement. */ +/** One logical instance owns this directory and key; never share or clone it across live instances. */ public final class ProviderStateStore implements AutoCloseable { private final Path directory, stateFile; private final FileChannel lockChannel; diff --git a/warden-signalling/src/main/resources/provider-registration-v0.schema.json b/warden-signalling/src/main/resources/provider-registration-v0.schema.json index b113cdf2..e5f46fcc 100644 --- a/warden-signalling/src/main/resources/provider-registration-v0.schema.json +++ b/warden-signalling/src/main/resources/provider-registration-v0.schema.json @@ -56,9 +56,6 @@ "type": "string", "maxLength": 128 }, - "bootstrapGrant": { - "type": "string" - }, "authorization": { "type": "object", "additionalProperties": false, @@ -69,8 +66,7 @@ "scheme": { "enum": [ "anonymous-proof-of-work", - "bearer-token", - "bootstrap-grant" + "bearer-token" ] } } @@ -117,29 +113,18 @@ }, "then": { "required": [ - "placement" + "placement", + "authorization" ], - "anyOf": [ - { - "required": [ - "bootstrapGrant" - ] - }, - { - "required": [ - "authorization" - ], + "properties": { + "authorization": { "properties": { - "authorization": { - "properties": { - "scheme": { - "const": "bearer-token" - } - } + "scheme": { + "const": "bearer-token" } } } - ] + } } } ], @@ -181,8 +166,7 @@ "scheme": { "enum": [ "anonymous-proof-of-work", - "bearer-token", - "bootstrap-grant" + "bearer-token" ] }, "modes": { @@ -239,8 +223,7 @@ "scheme": { "enum": [ "anonymous-proof-of-work", - "bearer-token", - "bootstrap-grant" + "bearer-token" ] }, "reference": { @@ -339,7 +322,7 @@ "mode", "profile", "label", - "grantId", + "authorizationId", "serviceId", "region", "pool", diff --git a/warden-signalling/src/test/java/dev/kastle/warden/admission/ProviderNativeBench.java b/warden-signalling/src/test/java/dev/kastle/warden/admission/ProviderNativeBench.java index 85755f58..961e47db 100644 --- a/warden-signalling/src/test/java/dev/kastle/warden/admission/ProviderNativeBench.java +++ b/warden-signalling/src/test/java/dev/kastle/warden/admission/ProviderNativeBench.java @@ -45,7 +45,7 @@ public static void main(String[] args) throws Exception { try { nativeHost = NativeProviderTransport.open(bootstrap, new InetSocketAddress("127.0.0.1", port), state.resolve("host-cert.pem"), state.resolve("host-key.pem"), new AdmissionGate.Limits(4, 8, 2, 10_000)).toCompletableFuture().get(10, TimeUnit.SECONDS); - provider = new ProviderClient(new ProviderClient.Configuration(origin, "warden-admission-v1", "Provider native integration", null, null, null), + provider = new ProviderClient(new ProviderClient.Configuration(origin, "warden-admission-v1", "Provider native integration"), new ProviderStateStore(state), nativeHost, () -> new ServerStatus("Automatic native server", 1234, "fixture-only", "Integration", 0, 4, 0), () -> new ProviderClient.Health(true, 4, 0, "nethernet", "provider-native-bench"), System.err::println); diff --git a/warden-signalling/src/test/java/org/cloudburstmc/netty/warden/IndependentProviderStub.java b/warden-signalling/src/test/java/org/cloudburstmc/netty/warden/IndependentProviderStub.java index 4d0736a5..4bc5c555 100644 --- a/warden-signalling/src/test/java/org/cloudburstmc/netty/warden/IndependentProviderStub.java +++ b/warden-signalling/src/test/java/org/cloudburstmc/netty/warden/IndependentProviderStub.java @@ -48,21 +48,21 @@ private JsonObject dispatch(HttpExchange e) throws Exception { d.add("operations", operations); JsonObject limits = new JsonObject(); limits.addProperty("heartbeatIntervalMs", 1000); if (checkInMillis > 0) limits.addProperty("checkInVersion", 1); limits.addProperty("maxControlPage", 100); limits.addProperty("leaseMs", 30000); limits.addProperty("maxBodyBytes", 65536); limits.addProperty("clockSkewMs", 60000); d.add("limits", limits); JsonObject policy = new JsonObject(); policy.addProperty("newServiceClaim", "none"); policy.addProperty("anonymousPow", true); policy.addProperty("attachmentPow", false); d.add("policy", policy); JsonObject authorization = new JsonObject(); authorization.addProperty("header", "Authorization"); JsonArray schemes = new JsonArray(); - schemes.add(authorizationScheme("anonymous-proof-of-work", "new-service")); schemes.add(authorizationScheme("bearer-token", "new-service", "attach-instance")); schemes.add(authorizationScheme("bootstrap-grant", "attach-instance")); authorization.add("schemes", schemes); d.add("authorization", authorization); return d; + schemes.add(authorizationScheme("anonymous-proof-of-work", "new-service")); schemes.add(authorizationScheme("bearer-token", "new-service", "attach-instance")); authorization.add("schemes", schemes); d.add("authorization", authorization); return d; } if (path.equals("/example/challenges") || path.equals("/example/recover")) { boolean recovery = path.endsWith("recover"); if (recovery && (registration == null || !registration.get("registrationId").equals(body.get("registrationId")))) throw new Failure(403, "recovery_unavailable"); JsonObject key = recovery ? keys.get(registration.get("keyId").getAsString()) : body.getAsJsonObject("publicKeyJwk"); - if (!recovery && !body.get("mode").getAsString().equals("new-service")) throw new Failure(403, "bootstrap_grant_required"); String authorization = recovery ? "recovery" : body.getAsJsonObject("authorization").get("scheme").getAsString(); + if (!recovery && body.get("mode").getAsString().equals("attach-instance") && !authorization.equals("bearer-token")) throw new Failure(403, "bearer_token_required"); if (authorization.equals("bearer-token")) { challengeAuthorization = e.getRequestHeaders().getFirst("Authorization"); if (!"Bearer independent-provider-token".equals(challengeAuthorization)) throw new Failure(401, "invalid_bearer_token"); } JsonObject c = new JsonObject(); c.addProperty("protocol", ProviderCrypto.PROTOCOL); c.addProperty("signature", ProviderCrypto.SIGNATURE); c.addProperty("challengeId", UUID.randomUUID().toString()); c.addProperty("audience", origin); c.addProperty("nonce", UUID.randomUUID().toString()); c.addProperty("thumbprint", ProviderCrypto.thumbprint(key)); c.addProperty("expiresAt", System.currentTimeMillis() + 60000); c.addProperty("serverTime", System.currentTimeMillis()); - JsonObject context = new JsonObject(); for (String f : List.of("label", "grantId", "serviceId", "region", "pool", "registrationId")) context.addProperty(f, ""); context.addProperty("mode", recovery ? "recover" : "new-service"); context.addProperty("profile", "example-profile-v0"); - if (!recovery && authorization.equals("bearer-token")) { context.addProperty("grantId", "independent-authority"); JsonObject selected = new JsonObject(); selected.addProperty("scheme", authorization); selected.addProperty("reference", "independent-authority"); c.add("authorization", selected); } + JsonObject context = new JsonObject(); for (String f : List.of("label", "authorizationId", "serviceId", "region", "pool", "registrationId")) context.addProperty(f, ""); context.addProperty("mode", recovery ? "recover" : body.get("mode").getAsString()); context.addProperty("profile", "example-profile-v0"); + if (!recovery && authorization.equals("bearer-token")) { context.addProperty("authorizationId", "independent-authority"); JsonObject selected = new JsonObject(); selected.addProperty("scheme", authorization); selected.addProperty("reference", "independent-authority"); c.add("authorization", selected); } if (!recovery && body.has("placement")) { JsonObject placement = body.getAsJsonObject("placement"); context.add("region", placement.get("region")); context.add("pool", placement.get("pool")); if (placement.has("tags")) { Map tags = new TreeMap<>(); for (var tag : placement.getAsJsonObject("tags").entrySet()) tags.put(tag.getKey(), tag.getValue().getAsString()); context.addProperty("tagsDigest", ProviderCrypto.tagsDigest(tags)); } } c.add("context", context); c.addProperty("contextDigest", ProviderCrypto.contextDigest(context)); JsonObject pow = new JsonObject(); pow.addProperty("algorithm", "sha256-leading-zero-bits-v0"); challengeDifficulty = recovery || authorization.equals("bearer-token") ? 0 : 2; pow.addProperty("difficulty", challengeDifficulty); c.add("pow", pow); challenges.put(c.get("challengeId").getAsString(), c.deepCopy()); keys.put(c.get("challengeId").getAsString(), key); diff --git a/warden-signalling/src/test/java/org/cloudburstmc/netty/warden/ProviderClientTest.java b/warden-signalling/src/test/java/org/cloudburstmc/netty/warden/ProviderClientTest.java index 4e77caad..407033f7 100644 --- a/warden-signalling/src/test/java/org/cloudburstmc/netty/warden/ProviderClientTest.java +++ b/warden-signalling/src/test/java/org/cloudburstmc/netty/warden/ProviderClientTest.java @@ -14,7 +14,7 @@ class ProviderClientTest { @Test void usesProviderNeutralBearerAuthorizationWithoutPowOrPersistingTheToken(@TempDir Path path) throws Exception { try (IndependentProviderStub stub = new IndependentProviderStub()) { var config = new ProviderClient.Configuration(URI.create(stub.origin), "example-profile-v0", "Hosted customer", - ProviderClient.NEW_SERVICE, ProviderClient.BEARER_TOKEN, "independent-provider-token", null, "EU", "customers", Map.of("plan", "premium")); + ProviderClient.NEW_SERVICE, ProviderClient.BEARER_TOKEN, "independent-provider-token", "EU", "customers", Map.of("plan", "premium")); ProviderClient client = new ProviderClient(config, new ProviderStateStore(path), new FakeTransport(), () -> null, () -> new ProviderClient.Health(true, 10, 0, "nethernet", "fixture"), message -> {}); try { @@ -31,7 +31,7 @@ class ProviderClientTest { @Test void refreshesAnExpiredOptionalClaimWithoutBlockingReadiness(@TempDir Path path) throws Exception { try (IndependentProviderStub stub = new IndependentProviderStub()) { stub.optionalClaim = true; - var config = new ProviderClient.Configuration(URI.create(stub.origin), "example-profile-v0", "Example", null, null, null); + var config = new ProviderClient.Configuration(URI.create(stub.origin), "example-profile-v0", "Example"); java.util.function.Supplier create = () -> { try { return new ProviderClient(config, new ProviderStateStore(path), new FakeTransport(), () -> null, () -> new ProviderClient.Health(true, 10, 0, "nethernet", "fixture"), message -> {}); } @@ -59,7 +59,7 @@ class ProviderClientTest { try (IndependentProviderStub stub = new IndependentProviderStub()) { stub.checkInMillis = 900000; AtomicInteger players = new AtomicInteger(0); FakeTransport transport = new FakeTransport(); transport.stateless = true; - ProviderClient client = new ProviderClient(new ProviderClient.Configuration(URI.create(stub.origin), "example-profile-v0", "Scheduled", null, null, null), + ProviderClient client = new ProviderClient(new ProviderClient.Configuration(URI.create(stub.origin), "example-profile-v0", "Scheduled"), new ProviderStateStore(path), transport, () -> new ServerStatus("Scheduled", 1234, "fixture", "world", players.get(), 40, 0), () -> new ProviderClient.Health(true, 40, players.get() / 40.0, "nethernet", "fixture"), message -> {}); try { @@ -82,7 +82,7 @@ class ProviderClientTest { int beforeRestart = stub.heartbeats; FakeTransport replacement = new FakeTransport(); replacement.stateless = true; stub.checkInMillis = 900000; - ProviderClient resumed = new ProviderClient(new ProviderClient.Configuration(URI.create(stub.origin), "example-profile-v0", "Scheduled", null, null, null), + ProviderClient resumed = new ProviderClient(new ProviderClient.Configuration(URI.create(stub.origin), "example-profile-v0", "Scheduled"), new ProviderStateStore(path), replacement, () -> new ServerStatus("Restarted", 1234, "fixture", "world", 0, 40, 0), () -> new ProviderClient.Health(true, 40, 0, "nethernet", "fixture"), message -> {}); try { @@ -114,7 +114,7 @@ public CompletionStage applyControl(JsonObject c) { @Test void portableRegistrationRefreshRotationAndRestart(@TempDir Path path) throws Exception { try (IndependentProviderStub stub = new IndependentProviderStub()) { AtomicInteger players = new AtomicInteger(2); FakeTransport host = new FakeTransport(); - var config = new ProviderClient.Configuration(URI.create(stub.origin), "example-profile-v0", "Example", null, null, null); + var config = new ProviderClient.Configuration(URI.create(stub.origin), "example-profile-v0", "Example"); ProviderClient client = new ProviderClient(config, new ProviderStateStore(path), host, () -> new ServerStatus("Example", 1234, "preview-fixture", "", players.get(), 40, 0), () -> new ProviderClient.Health(true, 100, 0.1, "nethernet", "fixture"), message -> {}); JsonObject registration = client.start().get(20, TimeUnit.SECONDS); assertEquals("example-machine-1", registration.get("instanceId").getAsString()); assertEquals(1, stub.registrations); assertEquals(1, host.installed); @@ -137,7 +137,7 @@ private static void eventually(java.util.function.BooleanSupplier condition) thr @Test void failedRefreshRetriesCoalescingAndPendingAdmissionRecovery(@TempDir Path path) throws Exception { try (IndependentProviderStub stub = new IndependentProviderStub()) { AtomicInteger players = new AtomicInteger(2); FakeTransport host = new FakeTransport(); - var config = new ProviderClient.Configuration(URI.create(stub.origin), "example-profile-v0", "Example", null, null, null); + var config = new ProviderClient.Configuration(URI.create(stub.origin), "example-profile-v0", "Example"); var health = (java.util.function.Supplier) () -> new ProviderClient.Health(true, 100, 0.1, "nethernet", "fixture"); ProviderClient client = new ProviderClient(config, new ProviderStateStore(path), host, () -> { if (players.get() < 0) throw new IllegalStateException("query unavailable"); @@ -176,7 +176,7 @@ private static void eventually(java.util.function.BooleanSupplier condition) thr @Test void localPersistenceFailureStopsPublicationAndClosesTransport(@TempDir Path path) throws Exception { try (IndependentProviderStub stub = new IndependentProviderStub()) { FakeTransport host = new FakeTransport(); - var client = new ProviderClient(new ProviderClient.Configuration(URI.create(stub.origin), "example-profile-v0", "Example", null, null, null), new ProviderStateStore(path), host, () -> null, () -> new ProviderClient.Health(true, 10, 0, "nethernet", "fixture"), message -> {}); + var client = new ProviderClient(new ProviderClient.Configuration(URI.create(stub.origin), "example-profile-v0", "Example"), new ProviderStateStore(path), host, () -> null, () -> new ProviderClient.Health(true, 10, 0, "nethernet", "fixture"), message -> {}); client.start().get(20, TimeUnit.SECONDS); java.nio.file.Files.move(path.resolve("provider-state.json"), path.resolve("saved-state.json")); java.nio.file.Files.createDirectory(path.resolve("provider-state.json")); diff --git a/warden-signalling/src/test/java/org/cloudburstmc/netty/warden/WardenProviderBench.java b/warden-signalling/src/test/java/org/cloudburstmc/netty/warden/WardenProviderBench.java index 21437b2c..6f92da20 100644 --- a/warden-signalling/src/test/java/org/cloudburstmc/netty/warden/WardenProviderBench.java +++ b/warden-signalling/src/test/java/org/cloudburstmc/netty/warden/WardenProviderBench.java @@ -13,7 +13,6 @@ public static void main(String[] args) throws Exception { if (!Set.of("127.0.0.1", "localhost", "[::1]").contains(provider.getHost())) throw new IllegalArgumentException("Fixture transport is loopback only"); Path state = Path.of(System.getProperty("providerState")); if (System.getProperty("providerMode", "once").equals("identity")) { try (var store = new ProviderStateStore(state)) { System.out.println(ProviderIdentity.initialize(store, provider)); } return; } - String grantPath = System.getProperty("providerGrantFile"), grant = grantPath == null ? null : Files.readString(Path.of(grantPath)).trim(); String token = System.getProperty("providerToken"); ProviderTransport transport = new ProviderTransport() { String keyId; @@ -27,14 +26,14 @@ public CompletionStage hostProfile() { public CompletionStage drain() { return CompletableFuture.completedFuture(null); } public CompletionStage close() { return CompletableFuture.completedFuture(null); } }; - String registrationMode = System.getProperty("providerRegistrationMode", grant == null && token == null ? ProviderClient.NEW_SERVICE : ProviderClient.ATTACH_INSTANCE); - String authorization = token != null ? ProviderClient.BEARER_TOKEN : grant != null ? ProviderClient.BOOTSTRAP_GRANT : ProviderClient.ANONYMOUS_PROOF_OF_WORK; + String registrationMode = System.getProperty("providerRegistrationMode", token == null ? ProviderClient.NEW_SERVICE : ProviderClient.ATTACH_INSTANCE); + String authorization = token == null ? ProviderClient.ANONYMOUS_PROOF_OF_WORK : ProviderClient.BEARER_TOKEN; Map tags = new TreeMap<>(); JsonObject configuredTags = JsonParser.parseString(System.getProperty("providerTags", "{}")).getAsJsonObject(); for (var entry : configuredTags.entrySet()) tags.put(entry.getKey(), entry.getValue().getAsString()); String region = System.getProperty("providerRegion", registrationMode.equals(ProviderClient.ATTACH_INSTANCE) ? "EU" : null); String pool = System.getProperty("providerPool", registrationMode.equals(ProviderClient.ATTACH_INSTANCE) ? "proxy" : null); - var config = new ProviderClient.Configuration(provider, "warden-admission-v1", "Java conformance backend", registrationMode, authorization, token, grant, region, pool, tags); + var config = new ProviderClient.Configuration(provider, "warden-admission-v1", "Java conformance backend", registrationMode, authorization, token, region, pool, tags); var client = new ProviderClient(config, new ProviderStateStore(state), transport, () -> new ServerStatus("Java bench", 1234, "fixture-only", "Fixture", 2, 50, 0), () -> new ProviderClient.Health(true, 100, 0.02, "nethernet", "java-conformance"), System.err::println); try { JsonObject registration = client.start().get(30, TimeUnit.SECONDS); diff --git a/warden-signalling/src/test/resources/provider-registration-v0.fixtures.json b/warden-signalling/src/test/resources/provider-registration-v0.fixtures.json index 1a0a0af2..933aee3f 100644 --- a/warden-signalling/src/test/resources/provider-registration-v0.fixtures.json +++ b/warden-signalling/src/test/resources/provider-registration-v0.fixtures.json @@ -7,8 +7,8 @@ ], "ext": true, "kty": "EC", - "x": "RKHXTHGwwJBKiOkXz86eQbrmZZENv6ZWc6u2KF04a8Sn2X_ZXxJ5hVNaDBjwq9ja", - "y": "Zz7lPI6wToxEXKGp3UmvG1YU5OT10sLjR9xtidUQ_FJg4tC3tKrhe08A08wjQcNZ", + "x": "7onqrvcQqP_J5uJk-j3M7KhZqAB3OwxxFkg2XodPmV9KmC7ALcVeK0CQ-pJqX88F", + "y": "7mSiEjHsP4o6StG48-3Vvb2BfG8WTuYxmfrYmaS_CZDVHoWibgPWvmkGUbVQG9xq", "crv": "P-384" }, "privateKeyJwk": { @@ -17,10 +17,10 @@ ], "ext": true, "kty": "EC", - "x": "RKHXTHGwwJBKiOkXz86eQbrmZZENv6ZWc6u2KF04a8Sn2X_ZXxJ5hVNaDBjwq9ja", - "y": "Zz7lPI6wToxEXKGp3UmvG1YU5OT10sLjR9xtidUQ_FJg4tC3tKrhe08A08wjQcNZ", + "x": "7onqrvcQqP_J5uJk-j3M7KhZqAB3OwxxFkg2XodPmV9KmC7ALcVeK0CQ-pJqX88F", + "y": "7mSiEjHsP4o6StG48-3Vvb2BfG8WTuYxmfrYmaS_CZDVHoWibgPWvmkGUbVQG9xq", "crv": "P-384", - "d": "Hrv5AY11JMEixKkBF_WPD7LjOTj45HS916I8HjTed26RgqS5TTym2VWwOqUSc5Fg" + "d": "yjEPqO0LygBnf919VCmdziREaPgeFeCT4M4QC_lW1Nvkda9iOoZHuRlQAUUGYua4" }, "challenge": { "protocol": "nethernet-provider-registration-v0", @@ -28,19 +28,19 @@ "challengeId": "challenge_fixture", "nonce": "8WH8OdS12DO6ju0_", "audience": "https://provider.example", - "thumbprint": "G-978TMXnR9ZTNyxvCHVjoNQOSjoI0FcDtL0Zxt0SFs", + "thumbprint": "FV0R5dHP--qigTPgspmBk6zImSz6BhG0368xJW7Gxlo", "context": { "mode": "attach-instance", "profile": "warden-admission-v1", "label": "EU café 🦊\n", - "grantId": "grant_fixture", + "authorizationId": "auth_fixture", "serviceId": "service_neutral", "region": "EU", "pool": "proxy", "registrationId": "", "tagsDigest": "CEHUNj2V3qUPpjRczgqi9TCVca5-uQ8g29G8YT4qtng" }, - "contextDigest": "YX4iETRL1CTWqj9eb1X8dCtwUftLOwBYip7wIWWq5z4", + "contextDigest": "DTwLRzAGOrGGijNIcREJkG3gAilzV11QII6FtjLSxk0", "expiresAt": 1788484800000, "serverTime": 1788484200000, "pow": { @@ -48,15 +48,15 @@ "difficulty": 0 }, "authorization": { - "scheme": "bootstrap-grant", - "reference": "grant_fixture" + "scheme": "bearer-token", + "reference": "auth_fixture" } }, "proofNonce": "0", "idempotencyKey": "intent_fixture_0001", "proof": { - "payload": "[\"nethernet-provider-registration-v0\",\"complete\",\"https://provider.example\",\"challenge_fixture\",\"8WH8OdS12DO6ju0_\",\"G-978TMXnR9ZTNyxvCHVjoNQOSjoI0FcDtL0Zxt0SFs\",\"YX4iETRL1CTWqj9eb1X8dCtwUftLOwBYip7wIWWq5z4\",1788484800000,\"0\",\"intent_fixture_0001\"]", - "signature": "9qlt-nUuPGdcrrkPzk0bhr9PGKXaYvyPibNNcNqIw5JJ22795YJZu9--HTIh_BRWvEgfXAIpuf3aMdqoza_XB1t46X1CIa-ElXREtXmVYYF9kffLuumIRItvdQ1CBi0i" + "payload": "[\"nethernet-provider-registration-v0\",\"complete\",\"https://provider.example\",\"challenge_fixture\",\"8WH8OdS12DO6ju0_\",\"FV0R5dHP--qigTPgspmBk6zImSz6BhG0368xJW7Gxlo\",\"DTwLRzAGOrGGijNIcREJkG3gAilzV11QII6FtjLSxk0\",1788484800000,\"0\",\"intent_fixture_0001\"]", + "signature": "kZ7PH1WjPxseqDY7ChIaCo9JpDLoTTOfFXxu4-U1BSQ8m5KFogOloM-AXWLqclDeMaFXcZGC4QSlu9kWiu-2Mq5sU0ZZxbRpfSbyMVVGMKoNcctSnLaY4Gv5CQ46kyso" }, "request": { "input": { @@ -72,6 +72,6 @@ "body": "{\"name\":\"café 🦊\",\"players\":0}\n" }, "payload": "[\"nethernet-provider-registration-v0\",\"provider-es384-v0\",\"https://provider.example\",\"POST\",\"/renew?region=EU&label=caf%C3%A9\",1788484200123,\"machine_neutral\",\"key_fixture\",\"intent_fixture_0001\",2,17,\"hW1_XbRZsu7XCnVbFpxNepqnGsOafEGkN_VFWwKb4jQ\"]", - "signature": "d1vzbLuWkSDPQWXwtg5z86d_e_LE-AYr9Fm_tzqZtLe28fTHNTqqDepqOC4ZJGtN0C0hi-Lzh85rtCmtzZH4QDXMJajWWBPs6mQvI_iPlpPDl0iC5DBiXkoEgMN5w5gv" + "signature": "Qz_zDSnNPc6gkPTMBu2WzKxh99JtrBFGwP6PwT3uYh3qK34M2CeFL2MKvRIecjd-gfhOBcNptORVo3jyl-g46f27rMKVimlBfpXY__a8m3u3rHQcHJq5gEoGbOS7MHuP" } } diff --git a/warden-signalling/src/test/resources/provider-registration-v0.schema.json b/warden-signalling/src/test/resources/provider-registration-v0.schema.json index b113cdf2..e5f46fcc 100644 --- a/warden-signalling/src/test/resources/provider-registration-v0.schema.json +++ b/warden-signalling/src/test/resources/provider-registration-v0.schema.json @@ -56,9 +56,6 @@ "type": "string", "maxLength": 128 }, - "bootstrapGrant": { - "type": "string" - }, "authorization": { "type": "object", "additionalProperties": false, @@ -69,8 +66,7 @@ "scheme": { "enum": [ "anonymous-proof-of-work", - "bearer-token", - "bootstrap-grant" + "bearer-token" ] } } @@ -117,29 +113,18 @@ }, "then": { "required": [ - "placement" + "placement", + "authorization" ], - "anyOf": [ - { - "required": [ - "bootstrapGrant" - ] - }, - { - "required": [ - "authorization" - ], + "properties": { + "authorization": { "properties": { - "authorization": { - "properties": { - "scheme": { - "const": "bearer-token" - } - } + "scheme": { + "const": "bearer-token" } } } - ] + } } } ], @@ -181,8 +166,7 @@ "scheme": { "enum": [ "anonymous-proof-of-work", - "bearer-token", - "bootstrap-grant" + "bearer-token" ] }, "modes": { @@ -239,8 +223,7 @@ "scheme": { "enum": [ "anonymous-proof-of-work", - "bearer-token", - "bootstrap-grant" + "bearer-token" ] }, "reference": { @@ -339,7 +322,7 @@ "mode", "profile", "label", - "grantId", + "authorizationId", "serviceId", "region", "pool",