Files
coder/codersdk/agentsdk/google.go
T
Spike Curtis 18945a7949 chore: refactor CLI agent auth tests as unit tests (#19609)
Fixes https://github.com/coder/internal/issues/933

Refactors CLI tests that check the `--auth` flag parsing for various public clouds into a unit test that just creates the agent Client and asserts on the type.

Testing that the agent client actually authenticates correctly with these auth types is well covered by Coderd tests, so we don't need to retread that ground here, and the deleted tests were flaky on Windows.
2025-09-03 10:49:19 +04:00

72 lines
2.3 KiB
Go

package agentsdk
import (
"context"
"encoding/json"
"fmt"
"net/http"
"cloud.google.com/go/compute/metadata"
"golang.org/x/xerrors"
"github.com/coder/coder/v2/codersdk"
)
type GoogleInstanceIdentityToken struct {
JSONWebToken string `json:"json_web_token" validate:"required"`
}
// GoogleSessionTokenExchanger exchanges a Google instance JWT document for a Coder session token.
// @typescript-ignore GoogleSessionTokenExchanger
type GoogleSessionTokenExchanger struct {
serviceAccount string
gcpClient *metadata.Client
client *codersdk.Client
}
func WithGoogleInstanceIdentity(serviceAccount string, gcpClient *metadata.Client) SessionTokenSetup {
return func(client *codersdk.Client) RefreshableSessionTokenProvider {
return &InstanceIdentitySessionTokenProvider{
TokenExchanger: &GoogleSessionTokenExchanger{
client: client,
gcpClient: gcpClient,
serviceAccount: serviceAccount,
},
}
}
}
// exchange uses the Google Compute Engine Metadata API to fetch a signed JWT, and exchange it for a session token for a
// workspace agent.
//
// The requesting instance must be registered as a resource in the latest history for a workspace.
func (g *GoogleSessionTokenExchanger) exchange(ctx context.Context) (AuthenticateResponse, error) {
if g.serviceAccount == "" {
// This is the default name specified by Google.
g.serviceAccount = "default"
}
gcpClient := metadata.NewClient(g.client.HTTPClient)
if g.gcpClient != nil {
gcpClient = g.gcpClient
}
// "format=full" is required, otherwise the responding payload will be missing "instance_id".
jwt, err := gcpClient.Get(fmt.Sprintf("instance/service-accounts/%s/identity?audience=coder&format=full", g.serviceAccount))
if err != nil {
return AuthenticateResponse{}, xerrors.Errorf("get metadata identity: %w", err)
}
// request without the token to avoid re-entering this function
res, err := g.client.RequestWithoutSessionToken(ctx, http.MethodPost, "/api/v2/workspaceagents/google-instance-identity", GoogleInstanceIdentityToken{
JSONWebToken: jwt,
})
if err != nil {
return AuthenticateResponse{}, err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return AuthenticateResponse{}, codersdk.ReadBodyAsError(res)
}
var resp AuthenticateResponse
return resp, json.NewDecoder(res.Body).Decode(&resp)
}