mirror of
https://github.com/coder/coder.git
synced 2025-07-12 00:14:10 +00:00
fix: Use environment variables for agent authentication (#1238)
* fix: Update GIT_COMMITTER_NAME to use username
This was a mistake when adding the committer fields 🤦.
* fix: Use environment variables for agent authentication
Using files led to situations where running "coder server --dev" would
break `gitssh`. This is applicable in a production environment too. Users
should be able to log into another Coder deployment from their workspace.
Users can still set "CODER_URL" if they'd like with agent env vars!
This commit is contained in:
@ -40,6 +40,7 @@ import (
|
|||||||
|
|
||||||
type Options struct {
|
type Options struct {
|
||||||
ReconnectingPTYTimeout time.Duration
|
ReconnectingPTYTimeout time.Duration
|
||||||
|
EnvironmentVariables map[string]string
|
||||||
Logger slog.Logger
|
Logger slog.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,6 +67,7 @@ func New(dialer Dialer, options *Options) io.Closer {
|
|||||||
logger: options.Logger,
|
logger: options.Logger,
|
||||||
closeCancel: cancelFunc,
|
closeCancel: cancelFunc,
|
||||||
closed: make(chan struct{}),
|
closed: make(chan struct{}),
|
||||||
|
envVars: options.EnvironmentVariables,
|
||||||
}
|
}
|
||||||
server.init(ctx)
|
server.init(ctx)
|
||||||
return server
|
return server
|
||||||
@ -83,23 +85,21 @@ type agent struct {
|
|||||||
closeMutex sync.Mutex
|
closeMutex sync.Mutex
|
||||||
closed chan struct{}
|
closed chan struct{}
|
||||||
|
|
||||||
// Environment variables sent by Coder to inject for shell sessions.
|
envVars map[string]string
|
||||||
// These are atomic because values can change after reconnect.
|
// metadata is atomic because values can change after reconnection.
|
||||||
envVars atomic.Value
|
metadata atomic.Value
|
||||||
ownerEmail atomic.String
|
|
||||||
ownerUsername atomic.String
|
|
||||||
startupScript atomic.Bool
|
startupScript atomic.Bool
|
||||||
sshServer *ssh.Server
|
sshServer *ssh.Server
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *agent) run(ctx context.Context) {
|
func (a *agent) run(ctx context.Context) {
|
||||||
var options Metadata
|
var metadata Metadata
|
||||||
var peerListener *peerbroker.Listener
|
var peerListener *peerbroker.Listener
|
||||||
var err error
|
var err error
|
||||||
// An exponential back-off occurs when the connection is failing to dial.
|
// An exponential back-off occurs when the connection is failing to dial.
|
||||||
// This is to prevent server spam in case of a coderd outage.
|
// This is to prevent server spam in case of a coderd outage.
|
||||||
for retrier := retry.New(50*time.Millisecond, 10*time.Second); retrier.Wait(ctx); {
|
for retrier := retry.New(50*time.Millisecond, 10*time.Second); retrier.Wait(ctx); {
|
||||||
options, peerListener, err = a.dialer(ctx, a.logger)
|
metadata, peerListener, err = a.dialer(ctx, a.logger)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, context.Canceled) {
|
if errors.Is(err, context.Canceled) {
|
||||||
return
|
return
|
||||||
@ -118,14 +118,12 @@ func (a *agent) run(ctx context.Context) {
|
|||||||
return
|
return
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
a.envVars.Store(options.EnvironmentVariables)
|
a.metadata.Store(metadata)
|
||||||
a.ownerEmail.Store(options.OwnerEmail)
|
|
||||||
a.ownerUsername.Store(options.OwnerUsername)
|
|
||||||
|
|
||||||
if a.startupScript.CAS(false, true) {
|
if a.startupScript.CAS(false, true) {
|
||||||
// The startup script has not ran yet!
|
// The startup script has not ran yet!
|
||||||
go func() {
|
go func() {
|
||||||
err := a.runStartupScript(ctx, options.StartupScript)
|
err := a.runStartupScript(ctx, metadata.StartupScript)
|
||||||
if errors.Is(err, context.Canceled) {
|
if errors.Is(err, context.Canceled) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -172,7 +170,7 @@ func (*agent) runStartupScript(ctx context.Context, script string) error {
|
|||||||
writer, err = gsyslog.NewLogger(gsyslog.LOG_INFO, "USER", "coder-startup-script")
|
writer, err = gsyslog.NewLogger(gsyslog.LOG_INFO, "USER", "coder-startup-script")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// If the syslog isn't supported or cannot be created, use a text file in temp.
|
// If the syslog isn't supported or cannot be created, use a text file in temp.
|
||||||
writer, err = os.CreateTemp("", "coder-startup-script.txt")
|
writer, err = os.CreateTemp("", "coder-startup-script-*.txt")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return xerrors.Errorf("open startup script log file: %w", err)
|
return xerrors.Errorf("open startup script log file: %w", err)
|
||||||
}
|
}
|
||||||
@ -319,6 +317,15 @@ func (a *agent) createCommand(ctx context.Context, rawCommand string, env []stri
|
|||||||
return nil, xerrors.Errorf("get user shell: %w", err)
|
return nil, xerrors.Errorf("get user shell: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rawMetadata := a.metadata.Load()
|
||||||
|
if rawMetadata == nil {
|
||||||
|
return nil, xerrors.Errorf("no metadata was provided: %w", err)
|
||||||
|
}
|
||||||
|
metadata, valid := rawMetadata.(Metadata)
|
||||||
|
if !valid {
|
||||||
|
return nil, xerrors.Errorf("metadata is the wrong type: %T", metadata)
|
||||||
|
}
|
||||||
|
|
||||||
// gliderlabs/ssh returns a command slice of zero
|
// gliderlabs/ssh returns a command slice of zero
|
||||||
// when a shell is requested.
|
// when a shell is requested.
|
||||||
command := rawCommand
|
command := rawCommand
|
||||||
@ -344,22 +351,23 @@ func (a *agent) createCommand(ctx context.Context, rawCommand string, env []stri
|
|||||||
cmd.Env = append(cmd.Env, fmt.Sprintf(`GIT_SSH_COMMAND=%s gitssh --`, executablePath))
|
cmd.Env = append(cmd.Env, fmt.Sprintf(`GIT_SSH_COMMAND=%s gitssh --`, executablePath))
|
||||||
// These prevent the user from having to specify _anything_ to successfully commit.
|
// These prevent the user from having to specify _anything_ to successfully commit.
|
||||||
// Both author and committer must be set!
|
// Both author and committer must be set!
|
||||||
cmd.Env = append(cmd.Env, fmt.Sprintf(`GIT_AUTHOR_EMAIL=%s`, a.ownerEmail.Load()))
|
cmd.Env = append(cmd.Env, fmt.Sprintf(`GIT_AUTHOR_EMAIL=%s`, metadata.OwnerEmail))
|
||||||
cmd.Env = append(cmd.Env, fmt.Sprintf(`GIT_COMMITTER_EMAIL=%s`, a.ownerEmail.Load()))
|
cmd.Env = append(cmd.Env, fmt.Sprintf(`GIT_COMMITTER_EMAIL=%s`, metadata.OwnerEmail))
|
||||||
cmd.Env = append(cmd.Env, fmt.Sprintf(`GIT_AUTHOR_NAME=%s`, a.ownerUsername.Load()))
|
cmd.Env = append(cmd.Env, fmt.Sprintf(`GIT_AUTHOR_NAME=%s`, metadata.OwnerUsername))
|
||||||
cmd.Env = append(cmd.Env, fmt.Sprintf(`GIT_COMMITTER_NAME=%s`, a.ownerUsername.Load()))
|
cmd.Env = append(cmd.Env, fmt.Sprintf(`GIT_COMMITTER_NAME=%s`, metadata.OwnerUsername))
|
||||||
|
|
||||||
// Load environment variables passed via the agent.
|
// Load environment variables passed via the agent.
|
||||||
// These should override all variables we manually specify.
|
// These should override all variables we manually specify.
|
||||||
envVars := a.envVars.Load()
|
for key, value := range metadata.EnvironmentVariables {
|
||||||
if envVars != nil {
|
|
||||||
envVarMap, ok := envVars.(map[string]string)
|
|
||||||
if ok {
|
|
||||||
for key, value := range envVarMap {
|
|
||||||
cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", key, value))
|
cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", key, value))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Agent-level environment variables should take over all!
|
||||||
|
// This is used for setting agent-specific variables like "CODER_AGENT_TOKEN".
|
||||||
|
for key, value := range a.envVars {
|
||||||
|
cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", key, value))
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return cmd, nil
|
return cmd, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
32
cli/agent.go
32
cli/agent.go
@ -21,17 +21,16 @@ import (
|
|||||||
|
|
||||||
func workspaceAgent() *cobra.Command {
|
func workspaceAgent() *cobra.Command {
|
||||||
var (
|
var (
|
||||||
rawURL string
|
|
||||||
auth string
|
auth string
|
||||||
token string
|
|
||||||
)
|
)
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "agent",
|
Use: "agent",
|
||||||
// This command isn't useful to manually execute.
|
// This command isn't useful to manually execute.
|
||||||
Hidden: true,
|
Hidden: true,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
if rawURL == "" {
|
rawURL, err := cmd.Flags().GetString(varAgentURL)
|
||||||
return xerrors.New("CODER_URL must be set")
|
if err != nil {
|
||||||
|
return xerrors.Errorf("CODER_AGENT_URL must be set: %w", err)
|
||||||
}
|
}
|
||||||
coderURL, err := url.Parse(rawURL)
|
coderURL, err := url.Parse(rawURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -46,8 +45,9 @@ func workspaceAgent() *cobra.Command {
|
|||||||
var exchangeToken func(context.Context) (codersdk.WorkspaceAgentAuthenticateResponse, error)
|
var exchangeToken func(context.Context) (codersdk.WorkspaceAgentAuthenticateResponse, error)
|
||||||
switch auth {
|
switch auth {
|
||||||
case "token":
|
case "token":
|
||||||
if token == "" {
|
token, err := cmd.Flags().GetString(varAgentToken)
|
||||||
return xerrors.Errorf("CODER_TOKEN must be set for token auth")
|
if err != nil {
|
||||||
|
return xerrors.Errorf("CODER_AGENT_TOKEN must be set for token auth: %w", err)
|
||||||
}
|
}
|
||||||
client.SessionToken = token
|
client.SessionToken = token
|
||||||
case "google-instance-identity":
|
case "google-instance-identity":
|
||||||
@ -115,27 +115,19 @@ func workspaceAgent() *cobra.Command {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg := createConfig(cmd)
|
|
||||||
err = cfg.AgentSession().Write(client.SessionToken)
|
|
||||||
if err != nil {
|
|
||||||
return xerrors.Errorf("writing agent session token to config: %w", err)
|
|
||||||
}
|
|
||||||
err = cfg.URL().Write(client.URL.String())
|
|
||||||
if err != nil {
|
|
||||||
return xerrors.Errorf("writing agent url to config: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
closer := agent.New(client.ListenWorkspaceAgent, &agent.Options{
|
closer := agent.New(client.ListenWorkspaceAgent, &agent.Options{
|
||||||
Logger: logger,
|
Logger: logger,
|
||||||
|
EnvironmentVariables: map[string]string{
|
||||||
|
// Override the "CODER_AGENT_TOKEN" variable in all
|
||||||
|
// shells so "gitssh" works!
|
||||||
|
"CODER_AGENT_TOKEN": client.SessionToken,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
<-cmd.Context().Done()
|
<-cmd.Context().Done()
|
||||||
return closer.Close()
|
return closer.Close()
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
cliflag.StringVarP(cmd.Flags(), &auth, "auth", "", "CODER_AUTH", "token", "Specify the authentication type to use for the agent")
|
cliflag.StringVarP(cmd.Flags(), &auth, "auth", "", "CODER_AGENT_AUTH", "token", "Specify the authentication type to use for the agent")
|
||||||
cliflag.StringVarP(cmd.Flags(), &rawURL, "url", "", "CODER_URL", "", "Specify the URL to access Coder")
|
|
||||||
cliflag.StringVarP(cmd.Flags(), &token, "token", "", "CODER_TOKEN", "", "Specifies the authentication token to access Coder")
|
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
@ -46,7 +46,7 @@ func TestWorkspaceAgent(t *testing.T) {
|
|||||||
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
|
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
|
||||||
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
|
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
|
||||||
|
|
||||||
cmd, _ := clitest.New(t, "agent", "--auth", "azure-instance-identity", "--url", client.URL.String())
|
cmd, _ := clitest.New(t, "agent", "--auth", "azure-instance-identity", "--agent-url", client.URL.String())
|
||||||
ctx, cancelFunc := context.WithCancel(context.Background())
|
ctx, cancelFunc := context.WithCancel(context.Background())
|
||||||
defer cancelFunc()
|
defer cancelFunc()
|
||||||
go func() {
|
go func() {
|
||||||
@ -100,7 +100,7 @@ func TestWorkspaceAgent(t *testing.T) {
|
|||||||
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
|
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
|
||||||
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
|
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
|
||||||
|
|
||||||
cmd, _ := clitest.New(t, "agent", "--auth", "aws-instance-identity", "--url", client.URL.String())
|
cmd, _ := clitest.New(t, "agent", "--auth", "aws-instance-identity", "--agent-url", client.URL.String())
|
||||||
ctx, cancelFunc := context.WithCancel(context.Background())
|
ctx, cancelFunc := context.WithCancel(context.Background())
|
||||||
defer cancelFunc()
|
defer cancelFunc()
|
||||||
go func() {
|
go func() {
|
||||||
@ -154,7 +154,7 @@ func TestWorkspaceAgent(t *testing.T) {
|
|||||||
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
|
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
|
||||||
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
|
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
|
||||||
|
|
||||||
cmd, _ := clitest.New(t, "agent", "--auth", "google-instance-identity", "--url", client.URL.String())
|
cmd, _ := clitest.New(t, "agent", "--auth", "google-instance-identity", "--agent-url", client.URL.String())
|
||||||
ctx, cancelFunc := context.WithCancel(context.Background())
|
ctx, cancelFunc := context.WithCancel(context.Background())
|
||||||
defer cancelFunc()
|
defer cancelFunc()
|
||||||
go func() {
|
go func() {
|
||||||
|
@ -19,6 +19,15 @@ import (
|
|||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// String sets a string flag on the given flag set.
|
||||||
|
func String(flagset *pflag.FlagSet, name, shorthand, env, def, usage string) {
|
||||||
|
v, ok := os.LookupEnv(env)
|
||||||
|
if !ok || v == "" {
|
||||||
|
v = def
|
||||||
|
}
|
||||||
|
flagset.StringP(name, shorthand, v, fmtUsage(usage, env))
|
||||||
|
}
|
||||||
|
|
||||||
// StringVarP sets a string flag on the given flag set.
|
// StringVarP sets a string flag on the given flag set.
|
||||||
func StringVarP(flagset *pflag.FlagSet, p *string, name string, shorthand string, env string, def string, usage string) {
|
func StringVarP(flagset *pflag.FlagSet, p *string, name string, shorthand string, env string, def string, usage string) {
|
||||||
v, ok := os.LookupEnv(env)
|
v, ok := os.LookupEnv(env)
|
||||||
|
@ -16,6 +16,28 @@ import (
|
|||||||
//nolint:paralleltest
|
//nolint:paralleltest
|
||||||
func TestCliflag(t *testing.T) {
|
func TestCliflag(t *testing.T) {
|
||||||
t.Run("StringDefault", func(t *testing.T) {
|
t.Run("StringDefault", func(t *testing.T) {
|
||||||
|
flagset, name, shorthand, env, usage := randomFlag()
|
||||||
|
def, _ := cryptorand.String(10)
|
||||||
|
cliflag.String(flagset, name, shorthand, env, def, usage)
|
||||||
|
got, err := flagset.GetString(name)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, def, got)
|
||||||
|
require.Contains(t, flagset.FlagUsages(), usage)
|
||||||
|
require.Contains(t, flagset.FlagUsages(), fmt.Sprintf(" - consumes $%s", env))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("StringEnvVar", func(t *testing.T) {
|
||||||
|
flagset, name, shorthand, env, usage := randomFlag()
|
||||||
|
envValue, _ := cryptorand.String(10)
|
||||||
|
t.Setenv(env, envValue)
|
||||||
|
def, _ := cryptorand.String(10)
|
||||||
|
cliflag.String(flagset, name, shorthand, env, def, usage)
|
||||||
|
got, err := flagset.GetString(name)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, envValue, got)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("StringVarPDefault", func(t *testing.T) {
|
||||||
var ptr string
|
var ptr string
|
||||||
flagset, name, shorthand, env, usage := randomFlag()
|
flagset, name, shorthand, env, usage := randomFlag()
|
||||||
def, _ := cryptorand.String(10)
|
def, _ := cryptorand.String(10)
|
||||||
@ -28,7 +50,7 @@ func TestCliflag(t *testing.T) {
|
|||||||
require.Contains(t, flagset.FlagUsages(), fmt.Sprintf(" - consumes $%s", env))
|
require.Contains(t, flagset.FlagUsages(), fmt.Sprintf(" - consumes $%s", env))
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("StringEnvVar", func(t *testing.T) {
|
t.Run("StringVarPEnvVar", func(t *testing.T) {
|
||||||
var ptr string
|
var ptr string
|
||||||
flagset, name, shorthand, env, usage := randomFlag()
|
flagset, name, shorthand, env, usage := randomFlag()
|
||||||
envValue, _ := cryptorand.String(10)
|
envValue, _ := cryptorand.String(10)
|
||||||
|
@ -21,10 +21,6 @@ func (r Root) Organization() File {
|
|||||||
return File(filepath.Join(string(r), "organization"))
|
return File(filepath.Join(string(r), "organization"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r Root) AgentSession() File {
|
|
||||||
return File(filepath.Join(string(r), "agentsession"))
|
|
||||||
}
|
|
||||||
|
|
||||||
// File provides convenience methods for interacting with *os.File.
|
// File provides convenience methods for interacting with *os.File.
|
||||||
type File string
|
type File string
|
||||||
|
|
||||||
|
@ -2,7 +2,6 @@ package cli
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strings"
|
"strings"
|
||||||
@ -11,7 +10,6 @@ import (
|
|||||||
"golang.org/x/xerrors"
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
"github.com/coder/coder/cli/cliui"
|
"github.com/coder/coder/cli/cliui"
|
||||||
"github.com/coder/coder/codersdk"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func gitssh() *cobra.Command {
|
func gitssh() *cobra.Command {
|
||||||
@ -20,22 +18,10 @@ func gitssh() *cobra.Command {
|
|||||||
Hidden: true,
|
Hidden: true,
|
||||||
Short: `Wraps the "ssh" command and uses the coder gitssh key for authentication`,
|
Short: `Wraps the "ssh" command and uses the coder gitssh key for authentication`,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
cfg := createConfig(cmd)
|
client, err := createAgentClient(cmd)
|
||||||
rawURL, err := cfg.URL().Read()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return xerrors.Errorf("read agent url from config: %w", err)
|
return xerrors.Errorf("create agent client: %w", err)
|
||||||
}
|
}
|
||||||
parsedURL, err := url.Parse(rawURL)
|
|
||||||
if err != nil {
|
|
||||||
return xerrors.Errorf("parse agent url from config: %w", err)
|
|
||||||
}
|
|
||||||
session, err := cfg.AgentSession().Read()
|
|
||||||
if err != nil {
|
|
||||||
return xerrors.Errorf("read agent session from config: %w", err)
|
|
||||||
}
|
|
||||||
client := codersdk.New(parsedURL)
|
|
||||||
client.SessionToken = session
|
|
||||||
|
|
||||||
key, err := client.AgentGitSSHKey(cmd.Context())
|
key, err := client.AgentGitSSHKey(cmd.Context())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return xerrors.Errorf("get agent git ssh token: %w", err)
|
return xerrors.Errorf("get agent git ssh token: %w", err)
|
||||||
|
@ -9,12 +9,10 @@ import (
|
|||||||
|
|
||||||
"github.com/gliderlabs/ssh"
|
"github.com/gliderlabs/ssh"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
gossh "golang.org/x/crypto/ssh"
|
gossh "golang.org/x/crypto/ssh"
|
||||||
|
|
||||||
"github.com/coder/coder/cli/clitest"
|
"github.com/coder/coder/cli/clitest"
|
||||||
"github.com/coder/coder/cli/config"
|
|
||||||
"github.com/coder/coder/coderd/coderdtest"
|
"github.com/coder/coder/coderd/coderdtest"
|
||||||
"github.com/coder/coder/codersdk"
|
"github.com/coder/coder/codersdk"
|
||||||
"github.com/coder/coder/provisioner/echo"
|
"github.com/coder/coder/provisioner/echo"
|
||||||
@ -61,7 +59,7 @@ func TestGitSSH(t *testing.T) {
|
|||||||
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
|
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
|
||||||
|
|
||||||
// start workspace agent
|
// start workspace agent
|
||||||
cmd, root := clitest.New(t, "agent", "--token", agentToken, "--url", client.URL.String())
|
cmd, root := clitest.New(t, "agent", "--agent-token", agentToken, "--agent-url", client.URL.String())
|
||||||
agentClient := &*client
|
agentClient := &*client
|
||||||
clitest.SetupConfig(t, agentClient, root)
|
clitest.SetupConfig(t, agentClient, root)
|
||||||
ctx, cancelFunc := context.WithCancel(context.Background())
|
ctx, cancelFunc := context.WithCancel(context.Background())
|
||||||
@ -92,7 +90,7 @@ func TestGitSSH(t *testing.T) {
|
|||||||
// as long as we get a successful session we don't care if the server errors
|
// as long as we get a successful session we don't care if the server errors
|
||||||
_ = ssh.Serve(l, func(s ssh.Session) {
|
_ = ssh.Serve(l, func(s ssh.Session) {
|
||||||
atomic.AddInt64(&inc, 1)
|
atomic.AddInt64(&inc, 1)
|
||||||
t.Log("got authenticated sesion")
|
t.Log("got authenticated session")
|
||||||
err := s.Exit(0)
|
err := s.Exit(0)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}, publicKeyOption)
|
}, publicKeyOption)
|
||||||
@ -101,22 +99,10 @@ func TestGitSSH(t *testing.T) {
|
|||||||
// start ssh session
|
// start ssh session
|
||||||
addr, ok := l.Addr().(*net.TCPAddr)
|
addr, ok := l.Addr().(*net.TCPAddr)
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
cfgDir := createConfig(cmd)
|
|
||||||
// set to agent config dir
|
// set to agent config dir
|
||||||
cmd, root = clitest.New(t, "gitssh", "--global-config="+string(cfgDir), "--", fmt.Sprintf("-p%d", addr.Port), "-o", "StrictHostKeyChecking=no", "127.0.0.1")
|
cmd, _ = clitest.New(t, "gitssh", "--agent-url", agentClient.URL.String(), "--agent-token", agentToken, "--", fmt.Sprintf("-p%d", addr.Port), "-o", "StrictHostKeyChecking=no", "127.0.0.1")
|
||||||
clitest.SetupConfig(t, agentClient, root)
|
|
||||||
|
|
||||||
err = cmd.ExecuteContext(context.Background())
|
err = cmd.ExecuteContext(context.Background())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.EqualValues(t, 1, inc)
|
require.EqualValues(t, 1, inc)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// createConfig consumes the global configuration flag to produce a config root.
|
|
||||||
func createConfig(cmd *cobra.Command) config.Root {
|
|
||||||
globalRoot, err := cmd.Flags().GetString("global-config")
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return config.Root(globalRoot)
|
|
||||||
}
|
|
||||||
|
56
cli/root.go
56
cli/root.go
@ -12,6 +12,7 @@ import (
|
|||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/coder/coder/buildinfo"
|
"github.com/coder/coder/buildinfo"
|
||||||
|
"github.com/coder/coder/cli/cliflag"
|
||||||
"github.com/coder/coder/cli/cliui"
|
"github.com/coder/coder/cli/cliui"
|
||||||
"github.com/coder/coder/cli/config"
|
"github.com/coder/coder/cli/config"
|
||||||
"github.com/coder/coder/codersdk"
|
"github.com/coder/coder/codersdk"
|
||||||
@ -22,6 +23,10 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
varURL = "url"
|
||||||
|
varToken = "token"
|
||||||
|
varAgentToken = "agent-token"
|
||||||
|
varAgentURL = "agent-url"
|
||||||
varGlobalConfig = "global-config"
|
varGlobalConfig = "global-config"
|
||||||
varNoOpen = "no-open"
|
varNoOpen = "no-open"
|
||||||
varForceTty = "force-tty"
|
varForceTty = "force-tty"
|
||||||
@ -76,27 +81,50 @@ func Root() *cobra.Command {
|
|||||||
workspaceAgent(),
|
workspaceAgent(),
|
||||||
)
|
)
|
||||||
|
|
||||||
cmd.PersistentFlags().String(varGlobalConfig, configdir.LocalConfig("coderv2"), "Path to the global `coder` config directory")
|
cliflag.String(cmd.PersistentFlags(), varURL, "", "CODER_URL", "", "Specify the URL to your deployment.")
|
||||||
cmd.PersistentFlags().Bool(varForceTty, false, "Force the `coder` command to run as if connected to a TTY")
|
cliflag.String(cmd.PersistentFlags(), varToken, "", "CODER_TOKEN", "", "Specify an authentication token.")
|
||||||
err := cmd.PersistentFlags().MarkHidden(varForceTty)
|
cliflag.String(cmd.PersistentFlags(), varAgentToken, "", "CODER_AGENT_TOKEN", "", "Specify an agent authentication token.")
|
||||||
if err != nil {
|
cliflag.String(cmd.PersistentFlags(), varAgentURL, "", "CODER_AGENT_URL", "", "Specify the URL for an agent to access your deployment.")
|
||||||
// This should never return an error, because we just added the `--force-tty`` flag prior to calling MarkHidden.
|
cliflag.String(cmd.PersistentFlags(), varGlobalConfig, "", "CODER_CONFIG_DIR", configdir.LocalConfig("coderv2"), "Specify the path to the global `coder` config directory.")
|
||||||
panic(err)
|
cmd.PersistentFlags().Bool(varForceTty, false, "Force the `coder` command to run as if connected to a TTY.")
|
||||||
}
|
_ = cmd.PersistentFlags().MarkHidden(varForceTty)
|
||||||
cmd.PersistentFlags().Bool(varNoOpen, false, "Block automatically opening URLs in the browser.")
|
cmd.PersistentFlags().Bool(varNoOpen, false, "Block automatically opening URLs in the browser.")
|
||||||
err = cmd.PersistentFlags().MarkHidden(varNoOpen)
|
_ = cmd.PersistentFlags().MarkHidden(varNoOpen)
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
// createClient returns a new client from the command context.
|
// createClient returns a new client from the command context.
|
||||||
// The configuration directory will be read from the global flag.
|
// It reads from global configuration files if flags are not set.
|
||||||
func createClient(cmd *cobra.Command) (*codersdk.Client, error) {
|
func createClient(cmd *cobra.Command) (*codersdk.Client, error) {
|
||||||
root := createConfig(cmd)
|
root := createConfig(cmd)
|
||||||
rawURL, err := root.URL().Read()
|
rawURL, err := cmd.Flags().GetString(varURL)
|
||||||
|
if err != nil || rawURL == "" {
|
||||||
|
rawURL, err = root.URL().Read()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
serverURL, err := url.Parse(rawURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
token, err := cmd.Flags().GetString(varToken)
|
||||||
|
if err != nil || token == "" {
|
||||||
|
token, err = root.Session().Read()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
client := codersdk.New(serverURL)
|
||||||
|
client.SessionToken = token
|
||||||
|
return client, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// createAgentClient returns a new client from the command context.
|
||||||
|
// It works just like createClient, but uses the agent token and URL instead.
|
||||||
|
func createAgentClient(cmd *cobra.Command) (*codersdk.Client, error) {
|
||||||
|
rawURL, err := cmd.Flags().GetString(varAgentURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -104,7 +132,7 @@ func createClient(cmd *cobra.Command) (*codersdk.Client, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
token, err := root.Session().Read()
|
token, err := cmd.Flags().GetString(varAgentToken)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -4,9 +4,10 @@ import (
|
|||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/coder/coder/cryptorand"
|
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
"golang.org/x/crypto/pbkdf2"
|
"golang.org/x/crypto/pbkdf2"
|
||||||
|
|
||||||
|
"github.com/coder/coder/cryptorand"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -2,7 +2,7 @@ terraform {
|
|||||||
required_providers {
|
required_providers {
|
||||||
coder = {
|
coder = {
|
||||||
source = "coder/coder"
|
source = "coder/coder"
|
||||||
version = "~> 0.3.1"
|
version = "0.3.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ terraform {
|
|||||||
required_providers {
|
required_providers {
|
||||||
coder = {
|
coder = {
|
||||||
source = "coder/coder"
|
source = "coder/coder"
|
||||||
version = "~> 0.3.1"
|
version = "0.3.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ terraform {
|
|||||||
required_providers {
|
required_providers {
|
||||||
coder = {
|
coder = {
|
||||||
source = "coder/coder"
|
source = "coder/coder"
|
||||||
version = "~> 0.3.1"
|
version = "0.3.4"
|
||||||
}
|
}
|
||||||
docker = {
|
docker = {
|
||||||
source = "kreuzwerker/docker"
|
source = "kreuzwerker/docker"
|
||||||
|
@ -2,7 +2,7 @@ terraform {
|
|||||||
required_providers {
|
required_providers {
|
||||||
coder = {
|
coder = {
|
||||||
source = "coder/coder"
|
source = "coder/coder"
|
||||||
version = "~> 0.3.1"
|
version = "0.3.4"
|
||||||
}
|
}
|
||||||
google = {
|
google = {
|
||||||
source = "hashicorp/google"
|
source = "hashicorp/google"
|
||||||
|
@ -2,7 +2,7 @@ terraform {
|
|||||||
required_providers {
|
required_providers {
|
||||||
coder = {
|
coder = {
|
||||||
source = "coder/coder"
|
source = "coder/coder"
|
||||||
version = "~> 0.3.1"
|
version = "0.3.4"
|
||||||
}
|
}
|
||||||
google = {
|
google = {
|
||||||
source = "hashicorp/google"
|
source = "hashicorp/google"
|
||||||
|
@ -2,7 +2,7 @@ terraform {
|
|||||||
required_providers {
|
required_providers {
|
||||||
coder = {
|
coder = {
|
||||||
source = "coder/coder"
|
source = "coder/coder"
|
||||||
version = "~> 0.3.1"
|
version = "0.3.4"
|
||||||
}
|
}
|
||||||
google = {
|
google = {
|
||||||
source = "hashicorp/google"
|
source = "hashicorp/google"
|
||||||
|
@ -120,7 +120,7 @@ func (t *terraform) Provision(stream proto.DRPCProvisioner_ProvisionStream) erro
|
|||||||
|
|
||||||
env := os.Environ()
|
env := os.Environ()
|
||||||
env = append(env,
|
env = append(env,
|
||||||
"CODER_URL="+start.Metadata.CoderUrl,
|
"CODER_AGENT_URL="+start.Metadata.CoderUrl,
|
||||||
"CODER_WORKSPACE_TRANSITION="+strings.ToLower(start.Metadata.WorkspaceTransition.String()),
|
"CODER_WORKSPACE_TRANSITION="+strings.ToLower(start.Metadata.WorkspaceTransition.String()),
|
||||||
"CODER_WORKSPACE_NAME="+start.Metadata.WorkspaceName,
|
"CODER_WORKSPACE_NAME="+start.Metadata.WorkspaceName,
|
||||||
"CODER_WORKSPACE_OWNER="+start.Metadata.WorkspaceOwner,
|
"CODER_WORKSPACE_OWNER="+start.Metadata.WorkspaceOwner,
|
||||||
|
@ -28,7 +28,7 @@ terraform {
|
|||||||
required_providers {
|
required_providers {
|
||||||
coder = {
|
coder = {
|
||||||
source = "coder/coder"
|
source = "coder/coder"
|
||||||
version = "0.3.1"
|
version = "0.3.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,8 +14,8 @@ var (
|
|||||||
"amd64": `$ProgressPreference = "SilentlyContinue"
|
"amd64": `$ProgressPreference = "SilentlyContinue"
|
||||||
Invoke-WebRequest -Uri ${ACCESS_URL}bin/coder-windows-amd64.exe -OutFile $env:TEMP\sshd.exe
|
Invoke-WebRequest -Uri ${ACCESS_URL}bin/coder-windows-amd64.exe -OutFile $env:TEMP\sshd.exe
|
||||||
Set-MpPreference -DisableRealtimeMonitoring $true -ExclusionPath $env:TEMP\sshd.exe
|
Set-MpPreference -DisableRealtimeMonitoring $true -ExclusionPath $env:TEMP\sshd.exe
|
||||||
$env:CODER_AUTH = "${AUTH_TYPE}"
|
$env:CODER_AGENT_AUTH = "${AUTH_TYPE}"
|
||||||
$env:CODER_URL = "${ACCESS_URL}"
|
$env:CODER_AGENT_URL = "${ACCESS_URL}"
|
||||||
Start-Process -FilePath $env:TEMP\sshd.exe -ArgumentList "agent" -PassThru`,
|
Start-Process -FilePath $env:TEMP\sshd.exe -ArgumentList "agent" -PassThru`,
|
||||||
},
|
},
|
||||||
"linux": {
|
"linux": {
|
||||||
@ -24,8 +24,8 @@ set -eu pipefail
|
|||||||
export BINARY_LOCATION=$(mktemp -d -t tmp.coderXXXXX)/coder
|
export BINARY_LOCATION=$(mktemp -d -t tmp.coderXXXXX)/coder
|
||||||
curl -fsSL ${ACCESS_URL}bin/coder-linux-amd64 -o $BINARY_LOCATION
|
curl -fsSL ${ACCESS_URL}bin/coder-linux-amd64 -o $BINARY_LOCATION
|
||||||
chmod +x $BINARY_LOCATION
|
chmod +x $BINARY_LOCATION
|
||||||
export CODER_AUTH="${AUTH_TYPE}"
|
export CODER_AGENT_AUTH="${AUTH_TYPE}"
|
||||||
export CODER_URL="${ACCESS_URL}"
|
export CODER_AGENT_URL="${ACCESS_URL}"
|
||||||
exec $BINARY_LOCATION agent`,
|
exec $BINARY_LOCATION agent`,
|
||||||
},
|
},
|
||||||
"darwin": {
|
"darwin": {
|
||||||
@ -34,8 +34,8 @@ set -eu pipefail
|
|||||||
export BINARY_LOCATION=$(mktemp -d -t tmp.coderXXXXX)/coder
|
export BINARY_LOCATION=$(mktemp -d -t tmp.coderXXXXX)/coder
|
||||||
curl -fsSL ${ACCESS_URL}bin/coder-darwin-amd64 -o $BINARY_LOCATION
|
curl -fsSL ${ACCESS_URL}bin/coder-darwin-amd64 -o $BINARY_LOCATION
|
||||||
chmod +x $BINARY_LOCATION
|
chmod +x $BINARY_LOCATION
|
||||||
export CODER_AUTH="${AUTH_TYPE}"
|
export CODER_AGENT_AUTH="${AUTH_TYPE}"
|
||||||
export CODER_URL="${ACCESS_URL}"
|
export CODER_AGENT_URL="${ACCESS_URL}"
|
||||||
exec $BINARY_LOCATION agent`,
|
exec $BINARY_LOCATION agent`,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user