From 2ebc4ec62439605560a385e59f60daede0cbea72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?An=C5=BEe=20Luzar?= Date: Wed, 26 Aug 2026 17:45:03 +0200 Subject: [PATCH 1/2] cmd/rofl/machine: Fix domain verification token derivation Since #696 `oasis rofl machine show` derived the custom domain verification token from `out.Replica.App`. That registration is fetched with `out.Provider.SchedulerApp` in order to read the proxy domain metadata, so it is the provider's scheduler app and not the app deployed on the machine. The scheduler derives the token from `deployment.app_id`, so the TXT record we printed could never be verified: the scheduler logs "TXT record not found", never adds the SNI mapping for the domain, and the app's ACME TLS-ALPN-01 challenge fails with "unexpected order status: Invalid" until it hits the Let's Encrypt rate limit. Derive the token from the instance's deployment again, while keeping the nil deployment handling that #696 was after. --- cmd/rofl/machine/show.go | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/cmd/rofl/machine/show.go b/cmd/rofl/machine/show.go index eb87f391..05e65566 100644 --- a/cmd/rofl/machine/show.go +++ b/cmd/rofl/machine/show.go @@ -128,7 +128,7 @@ func prettyPrintMachine(mCfg *machineCfg, out *machineShowOutput) { fmt.Printf("Proxy:\n") fmt.Printf(" Domain: %s\n", proxyDomain) - prettyPrintMachinePorts(mCfg.ExtraCfg, out.Replica.App, out.Machine, proxyDomain) + prettyPrintMachinePorts(mCfg.ExtraCfg, out.Machine, proxyDomain) } } @@ -209,7 +209,7 @@ func prettyPrintMachine(mCfg *machineCfg, out *machineShowOutput) { } } -func prettyPrintMachinePorts(extraCfg *roflCmdBuild.AppExtraConfig, appID rofl.AppID, insDsc *roflmarket.Instance, domain string) { +func prettyPrintMachinePorts(extraCfg *roflCmdBuild.AppExtraConfig, insDsc *roflmarket.Instance, domain string) { if extraCfg == nil || len(extraCfg.Ports) == 0 { return } @@ -223,12 +223,18 @@ func prettyPrintMachinePorts(extraCfg *roflCmdBuild.AppExtraConfig, appID rofl.A default: fmt.Printf(" %s (%s): https://%s\n", p.Port, p.ServiceName, p.CustomDomain) - domainToken := scheduler.DomainVerificationToken(insDsc, appID, p.CustomDomain) addrs, err := net.LookupHost(genericDomain) if err == nil { fmt.Printf(" * Point A record of your domain to: %s\n", addrs[0]) } - fmt.Printf(" * Add TXT record to your domain: oasis-rofl-verification=%s\n", domainToken) + + switch insDsc.Deployment { + case nil: + fmt.Printf(" * Deploy the app to obtain the TXT record for your domain\n") + default: + domainToken := scheduler.DomainVerificationToken(insDsc, insDsc.Deployment.AppID, p.CustomDomain) + fmt.Printf(" * Add TXT record to your domain: oasis-rofl-verification=%s\n", domainToken) + } } if i < len(extraCfg.Ports)-1 { From 46f7ffe2365ad8a4ad04ae0cd05a1c1216215d92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?An=C5=BEe=20Luzar?= Date: Wed, 26 Aug 2026 17:45:04 +0200 Subject: [PATCH 2/2] build/rofl/scheduler: Add domain verification token tests Pin the derivation against a token that a live rofl-scheduler 0.9.0 accepted, so the Go implementation cannot drift from the scheduler's Rust one unnoticed, and assert that the token is bound to the app it is derived from. --- build/rofl/scheduler/domain_test.go | 65 +++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 build/rofl/scheduler/domain_test.go diff --git a/build/rofl/scheduler/domain_test.go b/build/rofl/scheduler/domain_test.go new file mode 100644 index 00000000..ac846888 --- /dev/null +++ b/build/rofl/scheduler/domain_test.go @@ -0,0 +1,65 @@ +package scheduler + +import ( + "encoding/hex" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/oasisprotocol/oasis-sdk/client-sdk/go/modules/rofl" + "github.com/oasisprotocol/oasis-sdk/client-sdk/go/modules/roflmarket" + "github.com/oasisprotocol/oasis-sdk/client-sdk/go/types" +) + +func testAppID(t *testing.T, raw string) rofl.AppID { + t.Helper() + + var appID rofl.AppID + require.NoError(t, appID.UnmarshalText([]byte(raw)), "app id must parse") + return appID +} + +func testInstance(t *testing.T, provider string, id string) *roflmarket.Instance { + t.Helper() + + var providerAddr types.Address + require.NoError(t, providerAddr.UnmarshalText([]byte(provider)), "provider address must parse") + + rawID, err := hex.DecodeString(id) + require.NoError(t, err, "instance id must parse") + require.Len(t, rawID, 8, "instance id must be 8 bytes") + + instance := &roflmarket.Instance{Provider: providerAddr} + copy(instance.ID[:], rawID) + return instance +} + +// TestDomainVerificationToken pins the derivation against a token that a live +// rofl-scheduler (v0.9.0) accepted, so any change here that breaks compatibility with +// the scheduler's Rust implementation shows up as a test failure. +func TestDomainVerificationToken(t *testing.T) { + instance := testInstance(t, "oasis1qp2ens0hsp7gh23wajxa4hpetkdek3swyyulyrmz", "0000000000000634") + deployedApp := testAppID(t, "rofl1qrmnjkx47f4tcfvfclnrtj2rad82akeum5jcpe8y") + + require.Equal(t, + "ofdkQl2NWpILtYBJBQC+rfQj68oyKgfkuu0PyJlKGYk=", + DomainVerificationToken(instance, deployedApp, "api.testnet.privana.finance"), + ) +} + +// TestDomainVerificationTokenBoundToDeployedApp ensures the token is bound to the app +// deployed on the machine. Deriving it from the provider's scheduler app instead +// yields a token the scheduler will never verify. +func TestDomainVerificationTokenBoundToDeployedApp(t *testing.T) { + instance := testInstance(t, "oasis1qp2ens0hsp7gh23wajxa4hpetkdek3swyyulyrmz", "0000000000000634") + deployedApp := testAppID(t, "rofl1qrmnjkx47f4tcfvfclnrtj2rad82akeum5jcpe8y") + schedulerApp := testAppID(t, "rofl1qrqw99h0f7az3hwt2cl7yeew3wtz0fxunu7luyfg") + + const domain = "api.testnet.privana.finance" + + require.NotEqual(t, + DomainVerificationToken(instance, deployedApp, domain), + DomainVerificationToken(instance, schedulerApp, domain), + "token must change with the app it is derived from", + ) +}