Skip to content

Commit 056a39b

Browse files
Bojanmdelapenya
andauthored
feat(redpanda): add bootstrap user account option (#2975)
* feat(redpanda): add bootstrap user account option feat(redpanda): rework the admin api auth feat(redpanda): rework the admin api auth * feat(redpanda): rename WithEnableAdminAPIAuthentication to WithAdminAPIAuthentication. Address PR feedback to cleanup tests * Update test name for non-superuser Co-authored-by: Manuel de la Peña <social.mdelapenya@gmail.com> * Update test name for invalid user case Co-authored-by: Manuel de la Peña <social.mdelapenya@gmail.com> * Update test case name for schema registry test Co-authored-by: Manuel de la Peña <social.mdelapenya@gmail.com> * Update test name for successful authentication case Co-authored-by: Manuel de la Peña <social.mdelapenya@gmail.com> * Update test name for invalid user case Co-authored-by: Manuel de la Peña <social.mdelapenya@gmail.com> * Update test name for non-superuser test Co-authored-by: Manuel de la Peña <social.mdelapenya@gmail.com> * Update test name for wrong mechanism Co-authored-by: Manuel de la Peña <social.mdelapenya@gmail.com> * Update test name for schema registry test Co-authored-by: Manuel de la Peña <social.mdelapenya@gmail.com> * Update test name for failed authentication Co-authored-by: Manuel de la Peña <social.mdelapenya@gmail.com> * Update test name for successful authentication Co-authored-by: Manuel de la Peña <social.mdelapenya@gmail.com> * feat(redpanda): make AdminAPIClient properties private and add WithAuthentication() and accessor methods * feat(redpanda): set EnableAdminAPIAuthentication private * feat(redpanda): set baseURL as public again * chore: use test names without spaces --------- Co-authored-by: Manuel de la Peña <social.mdelapenya@gmail.com>
1 parent 66a55a8 commit 056a39b

File tree

5 files changed

+261
-69
lines changed

5 files changed

+261
-69
lines changed

docs/modules/redpanda.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,3 +182,8 @@ is an HTTP-based API and thus the returned format will be: http://host:port.
182182
<!--codeinclude-->
183183
[Get admin API address](../../modules/redpanda/redpanda_test.go) inside_block:adminAPIAddress
184184
<!--/codeinclude-->
185+
186+
#### WithAdminAPIAuthentication
187+
188+
Enables Admin API Authentication by setting [`admin_api_require_auth`](https://docs.redpanda.com/current/reference/properties/cluster-properties/#admin_api_require_auth) cluster configuration property to `true`.
189+
It also configures a bootstrap superuser account via [`RP_BOOTSTRAP_USER`](https://docs.redpanda.com/current/deploy/deployment-option/self-hosted/manual/production/production-deployment/#bootstrap-a-user-account) environment variable.

modules/redpanda/admin_api.go

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,29 +10,52 @@ import (
1010
"net/url"
1111
)
1212

13+
// AdminAPIClient is a client for the Redpanda Admin API.
1314
type AdminAPIClient struct {
14-
BaseURL string
15-
client *http.Client
15+
BaseURL string
16+
username string
17+
password string
18+
client *http.Client
1619
}
1720

21+
// NewAdminAPIClient creates a new AdminAPIClient.
1822
func NewAdminAPIClient(baseURL string) *AdminAPIClient {
1923
return &AdminAPIClient{
2024
BaseURL: baseURL,
2125
client: http.DefaultClient,
2226
}
2327
}
2428

29+
// WithHTTPClient sets the HTTP client for the AdminAPIClient.
2530
func (cl *AdminAPIClient) WithHTTPClient(c *http.Client) *AdminAPIClient {
2631
cl.client = c
2732
return cl
2833
}
2934

35+
// WithAuthentication sets the username and password for the AdminAPIClient.
36+
func (cl *AdminAPIClient) WithAuthentication(username, password string) *AdminAPIClient {
37+
cl.username = username
38+
cl.password = password
39+
return cl
40+
}
41+
42+
// Username returns the username of the AdminAPIClient.
43+
func (cl *AdminAPIClient) Username() string {
44+
return cl.username
45+
}
46+
47+
// Password returns the password of the AdminAPIClient.
48+
func (cl *AdminAPIClient) Password() string {
49+
return cl.password
50+
}
51+
3052
type createUserRequest struct {
3153
User string `json:"username,omitempty"`
3254
Password string `json:"password"`
3355
Algorithm string `json:"algorithm"`
3456
}
3557

58+
// CreateUser creates a new user in Redpanda using Admin API.
3659
func (cl *AdminAPIClient) CreateUser(ctx context.Context, username, password string) error {
3760
userReq := createUserRequest{
3861
User: username,
@@ -55,6 +78,10 @@ func (cl *AdminAPIClient) CreateUser(ctx context.Context, username, password str
5578
}
5679
req.Header.Set("Content-Type", "application/json")
5780

81+
if cl.username != "" || cl.password != "" {
82+
req.SetBasicAuth(cl.username, cl.password)
83+
}
84+
5885
resp, err := cl.client.Do(req)
5986
if err != nil {
6087
return fmt.Errorf("request failed: %w", err)

modules/redpanda/options.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ type options struct {
4747
// ExtraBootstrapConfig is a map of configs to be interpolated into the
4848
// container's bootstrap.yml
4949
ExtraBootstrapConfig map[string]any
50+
51+
// enableAdminAPIAuthentication enables Admin API authentication
52+
enableAdminAPIAuthentication bool
5053
}
5154

5255
func defaultOptions() options {
@@ -170,3 +173,12 @@ func WithBootstrapConfig(cfg string, val any) Option {
170173
o.ExtraBootstrapConfig[cfg] = val
171174
}
172175
}
176+
177+
// WithAdminAPIAuthentication enables Admin API Authentication.
178+
// It sets `admin_api_require_auth` configuration to true and configures a bootstrap user account.
179+
// See https://docs.redpanda.com/current/deploy/deployment-option/self-hosted/manual/production/production-deployment/#bootstrap-a-user-account
180+
func WithAdminAPIAuthentication() Option {
181+
return func(o *options) {
182+
o.enableAdminAPIAuthentication = true
183+
}
184+
}

modules/redpanda/redpanda.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ const (
4444
bootstrapConfigFile = ".bootstrap.yaml"
4545
certFile = "cert.pem"
4646
keyFile = "key.pem"
47+
48+
bootstrapAdminAPIUser = "redpanda_bootstrap_admin_user"
49+
bootstrapAdminAPIPassword = "redpanda_bootstrap_admin_password"
4750
)
4851

4952
// Container represents the Redpanda container type used in the module.
@@ -107,6 +110,25 @@ func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustom
107110
settings.EnableWasmTransform = false
108111
}
109112

113+
// 2.2. If enabled, bootstrap user account
114+
if settings.enableAdminAPIAuthentication {
115+
// set the RP_BOOTSTRAP_USER env var
116+
if req.Env == nil {
117+
req.Env = map[string]string{}
118+
}
119+
req.Env["RP_BOOTSTRAP_USER"] = bootstrapAdminAPIUser + ":" + bootstrapAdminAPIPassword
120+
121+
// add our internal bootstrap admin user to superusers
122+
settings.Superusers = append(settings.Superusers, bootstrapAdminAPIUser)
123+
124+
// enable admin_api_require_auth
125+
if settings.ExtraBootstrapConfig == nil {
126+
settings.ExtraBootstrapConfig = map[string]any{}
127+
}
128+
129+
settings.ExtraBootstrapConfig["admin_api_require_auth"] = true
130+
}
131+
110132
// 3. Register extra kafka listeners if provided, network aliases will be
111133
// set
112134
if err := registerListeners(settings, req); err != nil {
@@ -231,6 +253,10 @@ func Run(ctx context.Context, img string, opts ...testcontainers.ContainerCustom
231253

232254
adminAPIUrl := fmt.Sprintf("%s://%v:%d", c.urlScheme, hostIP, adminAPIPort.Int())
233255
adminCl := NewAdminAPIClient(adminAPIUrl)
256+
if settings.enableAdminAPIAuthentication {
257+
adminCl = adminCl.WithAuthentication(bootstrapAdminAPIUser, bootstrapAdminAPIPassword)
258+
}
259+
234260
if settings.EnableTLS {
235261
adminCl = adminCl.WithHTTPClient(&http.Client{
236262
Timeout: 5 * time.Second,

0 commit comments

Comments
 (0)