Skip to content

Commit 538d2fc

Browse files
committed
Add header-process flag
This allows specifying a command to run that can output headers for cases where users require dynamic headers (like to authenticate to their VPN). The primary use case is to add this flag in SSH configs created by the VS Code plugin, although maybe config-ssh should do the same.
1 parent 6af6e85 commit 538d2fc

File tree

7 files changed

+74
-13
lines changed

7 files changed

+74
-13
lines changed

cli/login.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ func (r *RootCmd) login() *clibase.Cmd {
7676
serverURL.Scheme = "https"
7777
}
7878

79-
client, err := r.createUnauthenticatedClient(serverURL)
79+
client, err := r.createUnauthenticatedClient(ctx, serverURL)
8080
if err != nil {
8181
return err
8282
}

cli/root.go

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"net/http"
1414
"net/url"
1515
"os"
16+
"os/exec"
1617
"os/signal"
1718
"path/filepath"
1819
"runtime"
@@ -55,6 +56,7 @@ const (
5556
varAgentToken = "agent-token"
5657
varAgentURL = "agent-url"
5758
varHeader = "header"
59+
varHeaderProcess = "header-process"
5860
varNoOpen = "no-open"
5961
varNoVersionCheck = "no-version-warning"
6062
varNoFeatureWarning = "no-feature-warning"
@@ -356,6 +358,13 @@ func (r *RootCmd) Command(subcommands []*clibase.Cmd) (*clibase.Cmd, error) {
356358
Value: clibase.StringArrayOf(&r.header),
357359
Group: globalGroup,
358360
},
361+
{
362+
Flag: varHeaderProcess,
363+
Env: "CODER_HEADER_PROCESS",
364+
Description: "An external process that outputs JSON-encoded key-value pairs to be used as additional HTTP headers added to all requests.",
365+
Value: clibase.StringOf(&r.headerProcess),
366+
Group: globalGroup,
367+
},
359368
{
360369
Flag: varNoOpen,
361370
Env: "CODER_NO_OPEN",
@@ -437,6 +446,7 @@ type RootCmd struct {
437446
token string
438447
globalConfig string
439448
header []string
449+
headerProcess string
440450
agentToken string
441451
agentURL *url.URL
442452
forceTTY bool
@@ -540,9 +550,7 @@ func (r *RootCmd) initClientInternal(client *codersdk.Client, allowTokenMissing
540550
return err
541551
}
542552
}
543-
err = r.setClient(
544-
client, r.clientURL,
545-
)
553+
err = r.setClient(inv.Context(), client, r.clientURL)
546554
if err != nil {
547555
return err
548556
}
@@ -592,7 +600,7 @@ func (r *RootCmd) initClientInternal(client *codersdk.Client, allowTokenMissing
592600
}
593601
}
594602

595-
func (r *RootCmd) setClient(client *codersdk.Client, serverURL *url.URL) error {
603+
func (r *RootCmd) setClient(ctx context.Context, client *codersdk.Client, serverURL *url.URL) error {
596604
transport := &headerTransport{
597605
transport: http.DefaultTransport,
598606
header: http.Header{},
@@ -604,16 +612,39 @@ func (r *RootCmd) setClient(client *codersdk.Client, serverURL *url.URL) error {
604612
}
605613
transport.header.Add(parts[0], parts[1])
606614
}
615+
if r.headerProcess != "" {
616+
shell := "sh"
617+
caller := "-c"
618+
if runtime.GOOS == "windows" {
619+
shell = "cmd.exe"
620+
caller = "/c"
621+
}
622+
// #nosec
623+
cmd := exec.CommandContext(ctx, shell, caller, r.headerProcess)
624+
cmd.Env = append(os.Environ(), "CODER_URL="+serverURL.String())
625+
out, err := cmd.Output()
626+
if err != nil {
627+
return xerrors.Errorf("failed to run %v (out: %q): %w", cmd.Args, out, err)
628+
}
629+
var headers map[string]string
630+
err = json.Unmarshal(out, &headers)
631+
if err != nil {
632+
return xerrors.Errorf("failed to parse json from %v (out: %q): %w", cmd.Args, out, err)
633+
}
634+
for key, value := range headers {
635+
transport.header.Add(key, value)
636+
}
637+
}
607638
client.URL = serverURL
608639
client.HTTPClient = &http.Client{
609640
Transport: transport,
610641
}
611642
return nil
612643
}
613644

614-
func (r *RootCmd) createUnauthenticatedClient(serverURL *url.URL) (*codersdk.Client, error) {
645+
func (r *RootCmd) createUnauthenticatedClient(ctx context.Context, serverURL *url.URL) (*codersdk.Client, error) {
615646
var client codersdk.Client
616-
err := r.setClient(&client, serverURL)
647+
err := r.setClient(ctx, &client, serverURL)
617648
return &client, err
618649
}
619650

cli/root_test.go

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"net/http"
77
"net/http/httptest"
8+
"runtime"
89
"strings"
910
"sync/atomic"
1011
"testing"
@@ -72,20 +73,28 @@ func TestRoot(t *testing.T) {
7273
t.Run("Header", func(t *testing.T) {
7374
t.Parallel()
7475

76+
var url string
7577
var called int64
7678
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
7779
atomic.AddInt64(&called, 1)
7880
assert.Equal(t, "wow", r.Header.Get("X-Testing"))
7981
assert.Equal(t, "Dean was Here!", r.Header.Get("Cool-Header"))
82+
assert.Equal(t, "very-wow-"+url, r.Header.Get("X-Process-Testing"))
8083
w.WriteHeader(http.StatusGone)
8184
}))
8285
defer srv.Close()
86+
url = srv.URL
8387
buf := new(bytes.Buffer)
88+
coderUrlEnv := "$CODER_URL"
89+
if runtime.GOOS == "windows" {
90+
coderUrlEnv = "%CODER_URL%"
91+
}
8492
inv, _ := clitest.New(t,
8593
"--no-feature-warning",
8694
"--no-version-warning",
8795
"--header", "X-Testing=wow",
8896
"--header", "Cool-Header=Dean was Here!",
97+
"--header-process", "printf '{\"X-Process-Testing\": \"very-wow-'"+coderUrlEnv+"'\"}'",
8998
"login", srv.URL,
9099
)
91100
inv.Stdout = buf
@@ -97,8 +106,8 @@ func TestRoot(t *testing.T) {
97106
})
98107
}
99108

100-
// TestDERPHeaders ensures that the client sends the global `--header`s to the
101-
// DERP server when connecting.
109+
// TestDERPHeaders ensures that the client sends the global `--header`s and
110+
// `--header-process` to the DERP server when connecting.
102111
func TestDERPHeaders(t *testing.T) {
103112
t.Parallel()
104113

@@ -129,8 +138,9 @@ func TestDERPHeaders(t *testing.T) {
129138
// Inject custom /derp handler so we can inspect the headers.
130139
var (
131140
expectedHeaders = map[string]string{
132-
"X-Test-Header": "test-value",
133-
"Cool-Header": "Dean was Here!",
141+
"X-Test-Header": "test-value",
142+
"Cool-Header": "Dean was Here!",
143+
"X-Process-Testing": "very-wow",
134144
}
135145
derpCalled int64
136146
)
@@ -159,9 +169,12 @@ func TestDERPHeaders(t *testing.T) {
159169
"--no-version-warning",
160170
"ping", workspace.Name,
161171
"-n", "1",
172+
"--header-process", "printf '{\"X-Process-Testing\": \"very-wow\"}'",
162173
}
163174
for k, v := range expectedHeaders {
164-
args = append(args, "--header", fmt.Sprintf("%s=%s", k, v))
175+
if k != "X-Process-Testing" {
176+
args = append(args, "--header", fmt.Sprintf("%s=%s", k, v))
177+
}
165178
}
166179
inv, root := clitest.New(t, args...)
167180
clitest.SetupConfig(t, client, root)

cli/testdata/coder_--help.golden

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,10 @@ variables or flags.
6262
Additional HTTP headers added to all requests. Provide as key=value.
6363
Can be specified multiple times.
6464

65+
--header-process string, $CODER_HEADER_PROCESS
66+
An external process that outputs JSON-encoded key-value pairs to be
67+
used as additional HTTP headers added to all requests.
68+
6569
--no-feature-warning bool, $CODER_NO_FEATURE_WARNING
6670
Suppress warnings about unlicensed features.
6771

cli/vscodessh.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ func (r *RootCmd) vscodeSSH() *clibase.Cmd {
8686
client.SetSessionToken(string(sessionToken))
8787

8888
// This adds custom headers to the request!
89-
err = r.setClient(client, serverURL)
89+
err = r.setClient(ctx, client, serverURL)
9090
if err != nil {
9191
return xerrors.Errorf("set client: %w", err)
9292
}

docs/cli.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,15 @@ Path to the global `coder` config directory.
9696

9797
Additional HTTP headers added to all requests. Provide as key=value. Can be specified multiple times.
9898

99+
### --header-process
100+
101+
| | |
102+
| ----------- | ---------------------------------- |
103+
| Type | <code>string</code> |
104+
| Environment | <code>$CODER_HEADER_PROCESS</code> |
105+
106+
An external process that outputs JSON-encoded key-value pairs to be used as additional HTTP headers added to all requests.
107+
99108
### --no-feature-warning
100109

101110
| | |

enterprise/cli/testdata/coder_--help.golden

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ variables or flags.
3333
Additional HTTP headers added to all requests. Provide as key=value.
3434
Can be specified multiple times.
3535

36+
--header-process string, $CODER_HEADER_PROCESS
37+
An external process that outputs JSON-encoded key-value pairs to be
38+
used as additional HTTP headers added to all requests.
39+
3640
--no-feature-warning bool, $CODER_NO_FEATURE_WARNING
3741
Suppress warnings about unlicensed features.
3842

0 commit comments

Comments
 (0)