Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 37f9d4b

Browse files
authored
feat: add --header-command flag (#9059)
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 b993cab commit 37f9d4b

File tree

7 files changed

+83
-14
lines changed

7 files changed

+83
-14
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: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package cli
22

33
import (
4+
"bufio"
5+
"bytes"
46
"context"
57
"encoding/base64"
68
"encoding/json"
@@ -13,6 +15,7 @@ import (
1315
"net/http"
1416
"net/url"
1517
"os"
18+
"os/exec"
1619
"os/signal"
1720
"path/filepath"
1821
"runtime"
@@ -55,6 +58,7 @@ const (
5558
varAgentToken = "agent-token"
5659
varAgentURL = "agent-url"
5760
varHeader = "header"
61+
varHeaderCommand = "header-command"
5862
varNoOpen = "no-open"
5963
varNoVersionCheck = "no-version-warning"
6064
varNoFeatureWarning = "no-feature-warning"
@@ -356,6 +360,13 @@ func (r *RootCmd) Command(subcommands []*clibase.Cmd) (*clibase.Cmd, error) {
356360
Value: clibase.StringArrayOf(&r.header),
357361
Group: globalGroup,
358362
},
363+
{
364+
Flag: varHeaderCommand,
365+
Env: "CODER_HEADER_COMMAND",
366+
Description: "An external command that outputs additional HTTP headers added to all requests. The command must output each header as `key=value` on its own line.",
367+
Value: clibase.StringOf(&r.headerCommand),
368+
Group: globalGroup,
369+
},
359370
{
360371
Flag: varNoOpen,
361372
Env: "CODER_NO_OPEN",
@@ -437,6 +448,7 @@ type RootCmd struct {
437448
token string
438449
globalConfig string
439450
header []string
451+
headerCommand string
440452
agentToken string
441453
agentURL *url.URL
442454
forceTTY bool
@@ -540,9 +552,7 @@ func (r *RootCmd) initClientInternal(client *codersdk.Client, allowTokenMissing
540552
return err
541553
}
542554
}
543-
err = r.setClient(
544-
client, r.clientURL,
545-
)
555+
err = r.setClient(inv.Context(), client, r.clientURL)
546556
if err != nil {
547557
return err
548558
}
@@ -592,12 +602,38 @@ func (r *RootCmd) initClientInternal(client *codersdk.Client, allowTokenMissing
592602
}
593603
}
594604

595-
func (r *RootCmd) setClient(client *codersdk.Client, serverURL *url.URL) error {
605+
func (r *RootCmd) setClient(ctx context.Context, client *codersdk.Client, serverURL *url.URL) error {
596606
transport := &headerTransport{
597607
transport: http.DefaultTransport,
598608
header: http.Header{},
599609
}
600-
for _, header := range r.header {
610+
headers := r.header
611+
if r.headerCommand != "" {
612+
shell := "sh"
613+
caller := "-c"
614+
if runtime.GOOS == "windows" {
615+
shell = "cmd.exe"
616+
caller = "/c"
617+
}
618+
var outBuf bytes.Buffer
619+
// #nosec
620+
cmd := exec.CommandContext(ctx, shell, caller, r.headerCommand)
621+
cmd.Env = append(os.Environ(), "CODER_URL="+serverURL.String())
622+
cmd.Stdout = &outBuf
623+
cmd.Stderr = io.Discard
624+
err := cmd.Run()
625+
if err != nil {
626+
return xerrors.Errorf("failed to run %v: %w", cmd.Args, err)
627+
}
628+
scanner := bufio.NewScanner(&outBuf)
629+
for scanner.Scan() {
630+
headers = append(headers, scanner.Text())
631+
}
632+
if err := scanner.Err(); err != nil {
633+
return xerrors.Errorf("scan %v: %w", cmd.Args, err)
634+
}
635+
}
636+
for _, header := range headers {
601637
parts := strings.SplitN(header, "=", 2)
602638
if len(parts) < 2 {
603639
return xerrors.Errorf("split header %q had less than two parts", header)
@@ -611,9 +647,9 @@ func (r *RootCmd) setClient(client *codersdk.Client, serverURL *url.URL) error {
611647
return nil
612648
}
613649

614-
func (r *RootCmd) createUnauthenticatedClient(serverURL *url.URL) (*codersdk.Client, error) {
650+
func (r *RootCmd) createUnauthenticatedClient(ctx context.Context, serverURL *url.URL) (*codersdk.Client, error) {
615651
var client codersdk.Client
616-
err := r.setClient(&client, serverURL)
652+
err := r.setClient(ctx, &client, serverURL)
617653
return &client, err
618654
}
619655

cli/root_test.go

Lines changed: 19 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,29 @@ 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"))
83+
assert.Equal(t, "more-wow", r.Header.Get("X-Process-Testing2"))
8084
w.WriteHeader(http.StatusGone)
8185
}))
8286
defer srv.Close()
87+
url = srv.URL
8388
buf := new(bytes.Buffer)
89+
coderURLEnv := "$CODER_URL"
90+
if runtime.GOOS == "windows" {
91+
coderURLEnv = "%CODER_URL%"
92+
}
8493
inv, _ := clitest.New(t,
8594
"--no-feature-warning",
8695
"--no-version-warning",
8796
"--header", "X-Testing=wow",
8897
"--header", "Cool-Header=Dean was Here!",
98+
"--header-command", "printf X-Process-Testing=very-wow-"+coderURLEnv+"'\\r\\n'X-Process-Testing2=more-wow",
8999
"login", srv.URL,
90100
)
91101
inv.Stdout = buf
@@ -97,8 +107,8 @@ func TestRoot(t *testing.T) {
97107
})
98108
}
99109

100-
// TestDERPHeaders ensures that the client sends the global `--header`s to the
101-
// DERP server when connecting.
110+
// TestDERPHeaders ensures that the client sends the global `--header`s and
111+
// `--header-command` to the DERP server when connecting.
102112
func TestDERPHeaders(t *testing.T) {
103113
t.Parallel()
104114

@@ -129,8 +139,9 @@ func TestDERPHeaders(t *testing.T) {
129139
// Inject custom /derp handler so we can inspect the headers.
130140
var (
131141
expectedHeaders = map[string]string{
132-
"X-Test-Header": "test-value",
133-
"Cool-Header": "Dean was Here!",
142+
"X-Test-Header": "test-value",
143+
"Cool-Header": "Dean was Here!",
144+
"X-Process-Testing": "very-wow",
134145
}
135146
derpCalled int64
136147
)
@@ -159,9 +170,12 @@ func TestDERPHeaders(t *testing.T) {
159170
"--no-version-warning",
160171
"ping", workspace.Name,
161172
"-n", "1",
173+
"--header-command", "printf X-Process-Testing=very-wow",
162174
}
163175
for k, v := range expectedHeaders {
164-
args = append(args, "--header", fmt.Sprintf("%s=%s", k, v))
176+
if k != "X-Process-Testing" {
177+
args = append(args, "--header", fmt.Sprintf("%s=%s", k, v))
178+
}
165179
}
166180
inv, root := clitest.New(t, args...)
167181
clitest.SetupConfig(t, client, root)

cli/testdata/coder_--help.golden

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

65+
--header-command string, $CODER_HEADER_COMMAND
66+
An external command that outputs additional HTTP headers added to all
67+
requests. The command must output each header as `key=value` on its
68+
own line.
69+
6570
--no-feature-warning bool, $CODER_NO_FEATURE_WARNING
6671
Suppress warnings about unlicensed features.
6772

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-command
100+
101+
| | |
102+
| ----------- | ---------------------------------- |
103+
| Type | <code>string</code> |
104+
| Environment | <code>$CODER_HEADER_COMMAND</code> |
105+
106+
An external command that outputs additional HTTP headers added to all requests. The command must output each header as `key=value` on its own line.
107+
99108
### --no-feature-warning
100109

101110
| | |

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

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

36+
--header-command string, $CODER_HEADER_COMMAND
37+
An external command that outputs additional HTTP headers added to all
38+
requests. The command must output each header as `key=value` on its
39+
own line.
40+
3641
--no-feature-warning bool, $CODER_NO_FEATURE_WARNING
3742
Suppress warnings about unlicensed features.
3843

0 commit comments

Comments
 (0)