Skip to content

Commit 7c6c6e4

Browse files
committed
roachtest: add helpers to extract sessionID and create default HTTPClient
These helpers are for roachtests to easily create a HTTPClient with appropriate cookies. It also sets InsecureSkipVerify to true as any certs we create won't be valid anyway. This change is so that we can create http requests to secure clusters. Release note: None
1 parent a30896a commit 7c6c6e4

File tree

4 files changed

+216
-2
lines changed

4 files changed

+216
-2
lines changed

pkg/cmd/roachtest/clusterstats/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ go_library(
1515
deps = [
1616
"//pkg/cmd/roachtest/cluster",
1717
"//pkg/cmd/roachtest/option",
18+
"//pkg/cmd/roachtest/roachtestutil",
1819
"//pkg/cmd/roachtest/test",
1920
"//pkg/roachprod/logger",
2021
"//pkg/roachprod/prometheus",

pkg/cmd/roachtest/roachtestutil/BUILD.bazel

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,12 @@ go_library(
2222
"//pkg/roachprod/install",
2323
"//pkg/roachprod/logger",
2424
"//pkg/testutils/sqlutils",
25+
"//pkg/util/httputil",
2526
"//pkg/util/humanizeutil",
2627
"//pkg/util/log",
2728
"//pkg/util/timeutil",
2829
"@com_github_cockroachdb_errors//:errors",
30+
"@com_github_pkg_errors//:errors",
2931
],
3032
)
3133

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
// Copyright 2024 The Cockroach Authors.
2+
//
3+
// Use of this software is governed by the Business Source License
4+
// included in the file licenses/BSL.txt.
5+
//
6+
// As of the Change Date specified in that file, in accordance with
7+
// the Business Source License, use of this software will be governed
8+
// by the Apache License, Version 2.0, included in the file
9+
// licenses/APL.txt.
10+
11+
package roachtestutil
12+
13+
import (
14+
"context"
15+
"crypto/tls"
16+
"fmt"
17+
"net/http"
18+
"net/http/cookiejar"
19+
"net/url"
20+
"strings"
21+
"time"
22+
23+
"github.com/cockroachdb/cockroach/pkg/cmd/roachtest/cluster"
24+
"github.com/cockroachdb/cockroach/pkg/cmd/roachtest/option"
25+
"github.com/cockroachdb/cockroach/pkg/cmd/roachtest/test"
26+
"github.com/cockroachdb/cockroach/pkg/roachprod/logger"
27+
"github.com/cockroachdb/cockroach/pkg/util/httputil"
28+
"github.com/cockroachdb/cockroach/pkg/util/protoutil"
29+
"github.com/cockroachdb/cockroach/pkg/util/syncutil"
30+
"github.com/cockroachdb/errors"
31+
)
32+
33+
// RoachtestHTTPClient is a wrapper over httputil.client that:
34+
// - Adds cookies for every request, allowing the client to
35+
// authenticate with secure clusters.
36+
// - Sets InsecureSkipVerify to true. Roachprod certs are not
37+
// signed by a verified root authority.
38+
// - Unsets DialContext and DisableKeepAlives set in httputil.
39+
// These two settings are not needed for testing purposes.
40+
//
41+
// This hides the details of creating authenticated https requests
42+
// from the roachtest user, as most of it is just boilerplate and
43+
// the same across all tests.
44+
//
45+
// Note that this currently only supports requests to CRDB clusters.
46+
type RoachtestHTTPClient struct {
47+
client *httputil.Client
48+
sessionID string
49+
cluster cluster.Cluster
50+
l *logger.Logger
51+
// Used for safely adding to the cookie jar.
52+
mu syncutil.Mutex
53+
}
54+
55+
type RoachtestHTTPOptions struct {
56+
Timeout time.Duration
57+
}
58+
59+
func HTTPTimeout(timeout time.Duration) func(options *RoachtestHTTPOptions) {
60+
return func(options *RoachtestHTTPOptions) {
61+
options.Timeout = timeout
62+
}
63+
}
64+
65+
// DefaultHTTPClient returns a default RoachtestHTTPClient.
66+
// This should be the default HTTP client used if you are
67+
// trying to make a request to a secure cluster.
68+
func DefaultHTTPClient(
69+
c cluster.Cluster, l *logger.Logger, opts ...func(options *RoachtestHTTPOptions),
70+
) *RoachtestHTTPClient {
71+
roachtestHTTP := RoachtestHTTPClient{
72+
client: httputil.NewClientWithTimeout(httputil.StandardHTTPTimeout),
73+
cluster: c,
74+
l: l,
75+
}
76+
77+
// Certificates created by roachprod start are not signed by
78+
// a verified root authority.
79+
roachtestHTTP.client.Transport = &http.Transport{
80+
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
81+
}
82+
83+
httpOptions := &RoachtestHTTPOptions{}
84+
for _, opt := range opts {
85+
opt(httpOptions)
86+
}
87+
if httpOptions.Timeout != 0 {
88+
roachtestHTTP.client.Timeout = httpOptions.Timeout
89+
}
90+
91+
return &roachtestHTTP
92+
}
93+
94+
func (r *RoachtestHTTPClient) Get(ctx context.Context, url string) (*http.Response, error) {
95+
if err := r.addCookie(ctx, url); err != nil {
96+
return nil, err
97+
}
98+
return r.client.Get(ctx, url)
99+
}
100+
101+
func (r *RoachtestHTTPClient) GetJSON(
102+
ctx context.Context, path string, response protoutil.Message,
103+
) error {
104+
if err := r.addCookie(ctx, path); err != nil {
105+
return err
106+
}
107+
return httputil.GetJSON(*r.client.Client, path, response)
108+
}
109+
110+
func (r *RoachtestHTTPClient) PostProtobuf(
111+
ctx context.Context, path string, request, response protoutil.Message,
112+
) error {
113+
if err := r.addCookie(ctx, path); err != nil {
114+
return err
115+
}
116+
return httputil.PostProtobuf(ctx, *r.client.Client, path, request, response)
117+
}
118+
119+
// ResetSession removes the saved sessionID so that it is retrieved
120+
// again the next time a request is made. This is usually not required
121+
// as the sessionID does not change unless the cluster is restarted.
122+
func (r *RoachtestHTTPClient) ResetSession() {
123+
r.sessionID = ""
124+
}
125+
126+
func (r *RoachtestHTTPClient) addCookie(ctx context.Context, cookieUrl string) error {
127+
// If we haven't extracted the sessionID yet, do so.
128+
if r.sessionID == "" {
129+
id, err := getSessionID(ctx, r.cluster, r.l, r.cluster.All())
130+
if err != nil {
131+
return errors.Wrapf(err, "roachtestutil.addCookie: unable to extract sessionID")
132+
}
133+
r.sessionID = id
134+
}
135+
136+
name, value, found := strings.Cut(r.sessionID, "=")
137+
if !found {
138+
return errors.New("Cookie not formatted correctly")
139+
}
140+
141+
url, err := url.Parse(cookieUrl)
142+
if err != nil {
143+
return errors.Wrapf(err, "roachtestutil.addCookie: unable to parse cookieUrl")
144+
}
145+
err = r.SetCookies(url, []*http.Cookie{{Name: name, Value: value}})
146+
if err != nil {
147+
return errors.Wrapf(err, "roachtestutil.addCookie: unable to set cookie")
148+
}
149+
150+
return nil
151+
}
152+
153+
// SetCookies is a helper that checks if a client.CookieJar exists and creates
154+
// one if it doesn't. It then sets the provided cookies through CookieJar.SetCookies.
155+
func (r *RoachtestHTTPClient) SetCookies(u *url.URL, cookies []*http.Cookie) error {
156+
r.mu.Lock()
157+
defer r.mu.Unlock()
158+
if r.client.Jar == nil {
159+
jar, err := cookiejar.New(nil)
160+
if err != nil {
161+
return err
162+
}
163+
r.client.Jar = jar
164+
}
165+
r.client.Jar.SetCookies(u, cookies)
166+
return nil
167+
}
168+
169+
func getSessionIDOnSingleNode(
170+
ctx context.Context, c cluster.Cluster, l *logger.Logger, node option.NodeListOption,
171+
) (string, error) {
172+
loginCmd := fmt.Sprintf(
173+
"%s auth-session login root --port={pgport%s} --certs-dir ./certs --format raw",
174+
test.DefaultCockroachPath, node,
175+
)
176+
res, err := c.RunWithDetailsSingleNode(ctx, l, option.WithNodes(node), loginCmd)
177+
if err != nil {
178+
return "", errors.Wrap(err, "failed to authenticate")
179+
}
180+
181+
var sessionCookie string
182+
for _, line := range strings.Split(res.Stdout, "\n") {
183+
if strings.HasPrefix(line, "session=") {
184+
sessionCookie = line
185+
}
186+
}
187+
if sessionCookie == "" {
188+
return "", fmt.Errorf("failed to find session cookie in `login` output")
189+
}
190+
191+
return sessionCookie, nil
192+
}
193+
194+
func getSessionID(
195+
ctx context.Context, c cluster.Cluster, l *logger.Logger, nodes option.NodeListOption,
196+
) (string, error) {
197+
var err error
198+
var cookie string
199+
// The session ID should be the same for all nodes so stop after we successfully
200+
// get it from one node.
201+
for _, node := range nodes {
202+
cookie, err = getSessionIDOnSingleNode(ctx, c, l, c.Node(node))
203+
if err == nil {
204+
break
205+
}
206+
l.Printf("%s auth session login failed on node %d: %v", test.DefaultCockroachPath, node, err)
207+
}
208+
if err != nil {
209+
return "", errors.Wrapf(err, "roachtestutil.GetSessionID")
210+
}
211+
sessionID := strings.Split(cookie, ";")[0]
212+
return sessionID, nil
213+
}

pkg/roachprod/install/cockroach.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -174,8 +174,6 @@ const (
174174
NoSQLTimeout = 0
175175

176176
defaultInitTarget = Node(1)
177-
178-
Username = "roach"
179177
)
180178

181179
func (st StartTarget) String() string {

0 commit comments

Comments
 (0)