diff --git a/common.go b/common.go index e215813..4e1ed00 100644 --- a/common.go +++ b/common.go @@ -540,12 +540,12 @@ func interpolateBlueprint(data []byte) []byte { }) } -func sendBlueprint(client *websocket.Client) error { - if blueprintFile == "" { +func sendBlueprint(client *websocket.Client, file string) error { + if file == "" { return nil } // try to read the blueprint file - blueprintData, err := os.ReadFile(blueprintFile) + blueprintData, err := os.ReadFile(file) if err != nil { logger.Error("Failed to read blueprint file: %v", err) } else { diff --git a/main.go b/main.go index a5c4581..d5f2a96 100644 --- a/main.go +++ b/main.go @@ -155,8 +155,9 @@ var ( region string metricsAsyncBytes bool pprofEnabled bool - blueprintFile string - noCloud bool + blueprintFile string + provisioningBlueprintFile string + noCloud bool // New mTLS configuration variables tlsClientCert string @@ -284,6 +285,7 @@ func runNewtMain(ctx context.Context) { tlsPrivateKey = os.Getenv("TLS_CLIENT_CERT") } blueprintFile = os.Getenv("BLUEPRINT_FILE") + provisioningBlueprintFile = os.Getenv("PROVISIONING_BLUEPRINT_FILE") noCloudEnv := os.Getenv("NO_CLOUD") noCloud = noCloudEnv == "true" provisioningKey = os.Getenv("NEWT_PROVISIONING_KEY") @@ -393,6 +395,9 @@ func runNewtMain(ctx context.Context) { if blueprintFile == "" { flag.StringVar(&blueprintFile, "blueprint-file", "", "Path to blueprint file (if unset, no blueprint will be applied)") } + if provisioningBlueprintFile == "" { + flag.StringVar(&provisioningBlueprintFile, "provisioning-blueprint-file", "", "Path to blueprint file applied once after a provisioning credential exchange (if unset, no provisioning blueprint will be applied)") + } if noCloudEnv == "" { flag.BoolVar(&noCloud, "no-cloud", false, "Disable cloud failover") } @@ -1821,7 +1826,11 @@ persistent_keepalive_interval=5`, util.FixKey(privateKey.String()), util.FixKey( logger.Warn("CLIENTS WILL NOT WORK ON THIS VERSION OF NEWT WITH THIS VERSION OF PANGOLIN, PLEASE UPDATE THE SERVER TO 1.13 OR HIGHER OR DOWNGRADE NEWT") } - sendBlueprint(client) + sendBlueprint(client, blueprintFile) + if client.WasJustProvisioned() { + logger.Info("Provisioning detected – sending provisioning blueprint") + sendBlueprint(client, provisioningBlueprintFile) + } } else { // Resend current health check status for all targets in case the server // missed updates while newt was disconnected. diff --git a/websocket/client.go b/websocket/client.go index 49cf414..6990bd2 100644 --- a/websocket/client.go +++ b/websocket/client.go @@ -53,6 +53,7 @@ type Client struct { processingMessage bool // Flag to track if a message is currently being processed processingMux sync.RWMutex // Protects processingMessage processingWg sync.WaitGroup // WaitGroup to wait for message processing to complete + justProvisioned bool // Set to true when provisionIfNeeded exchanges a key for permanent credentials } type ClientOption func(*Client) @@ -102,6 +103,16 @@ func (c *Client) OnTokenUpdate(callback func(token string)) { c.onTokenUpdate = callback } +// WasJustProvisioned reports whether the client exchanged a provisioning key +// for permanent credentials during the most recent connection attempt. It +// consumes the flag – subsequent calls return false until provisioning occurs +// again (which, in practice, never happens once credentials are persisted). +func (c *Client) WasJustProvisioned() bool { + v := c.justProvisioned + c.justProvisioned = false + return v +} + func (c *Client) metricsContext() context.Context { c.metricsCtxMu.RLock() defer c.metricsCtxMu.RUnlock() diff --git a/websocket/config.go b/websocket/config.go index d727a41..39f1bd2 100644 --- a/websocket/config.go +++ b/websocket/config.go @@ -264,6 +264,7 @@ func (c *Client) provisionIfNeeded() error { c.config.ProvisioningKey = "" c.config.Name = "" c.configNeedsSave = true + c.justProvisioned = true // Save immediately so that if the subsequent connection attempt fails the // provisioning key is already gone from disk and the next retry uses the