mirror of
https://github.com/coder/coder.git
synced 2026-06-03 21:18:24 +00:00
18945a7949
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.
72 lines
2.3 KiB
Go
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)
|
|
}
|