Skip to content

Commit 0e10834

Browse files
authored
feat: add flags to the plugin and implement show config token flag (#168)
* feat: add flags to the plugin and implement show config token flag * test: flags for plugin * docs: added flags
1 parent 19620ef commit 0e10834

11 files changed

+263
-11
lines changed

README.md

+9-1
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,14 @@ you may or may not be able to access certain paths.
8181
^token/(?P<role_name>\w(([\w-.]+)?\w)?)$
8282
Generate an access token based on the specified role
8383
```
84+
## Flags
85+
86+
There are some flags we can specify to enable/disable some functionality in the vault plugin.
87+
88+
89+
| Flag | Default value | Description |
90+
|:-----------------:|:-------------:|:---------------------------------------------------------------------------------------|
91+
| show-config-token | false | Display the token value when reading a config on it's endpoint like `/config/default`. |
8492
8593
## Security Model
8694
@@ -370,7 +378,7 @@ token_sha1_hash 91a91bb30f816770081c570504c5e2723bcb1f38
370378
type self-managed
371379
```
372380
373-
**Important**: Token will be shown only after rotation, and it will not be shown again.
381+
**Important**: Token will be shown only after rotation, and it will not be shown again. (Unless the plugin is started with the `-show-config-token` flag)
374382
375383
## Upgrading
376384

backend.go

+10-1
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,18 @@ with the "^config/(?P<config_name>\w(([\w-.@]+)?\w)?)$" endpoints.
2727
`
2828
)
2929

30+
func Factory(flags Flags) logical.Factory {
31+
return func(ctx context.Context, config *logical.BackendConfig) (logical.Backend, error) {
32+
return factory(ctx, config, flags)
33+
}
34+
}
35+
3036
// Factory returns expected new Backend as logical.Backend
31-
func Factory(ctx context.Context, conf *logical.BackendConfig) (logical.Backend, error) {
37+
func factory(ctx context.Context, conf *logical.BackendConfig, flags Flags) (logical.Backend, error) {
3238
var b = &Backend{
3339
roleLocks: locksutil.CreateLocks(),
3440
clients: sync.Map{},
41+
flags: flags,
3542
}
3643

3744
b.Backend = &framework.Backend{
@@ -74,6 +81,8 @@ func Factory(ctx context.Context, conf *logical.BackendConfig) (logical.Backend,
7481
type Backend struct {
7582
*framework.Backend
7683

84+
flags Flags
85+
7786
// The client that we can use to create and revoke the access tokens
7887
clients sync.Map
7988

cmd/vault-plugin-secrets-gitlab/main.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,18 @@ var (
1717
func main() {
1818
apiClientMeta := &api.PluginAPIClientMeta{}
1919
flags := apiClientMeta.FlagSet()
20+
pf := &gat.Flags{}
21+
pf.FlagSet(flags)
2022

2123
fatalIfError(flags.Parse(os.Args[1:]))
2224

2325
tlsConfig := apiClientMeta.GetTLSConfig()
2426
tlsProviderFunc := api.VaultPluginTLSProvider(tlsConfig)
2527

2628
fatalIfError(plugin.ServeMultiplex(&plugin.ServeOpts{
27-
BackendFactoryFunc: gat.Factory,
29+
BackendFactoryFunc: gat.Factory(*pf),
2830
TLSProviderFunc: tlsProviderFunc,
31+
Logger: logger,
2932
}))
3033
}
3134

entry_config.go

+8-2
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ func (e *EntryConfig) UpdateFromFieldData(data *framework.FieldData) (warnings [
139139
return warnings, err
140140
}
141141

142-
func (e *EntryConfig) LogicalResponseData() map[string]any {
142+
func (e *EntryConfig) LogicalResponseData(includeToken bool) (data map[string]any) {
143143
var tokenExpiresAt, tokenCreatedAt = "", ""
144144
if !e.TokenExpiresAt.IsZero() {
145145
tokenExpiresAt = e.TokenExpiresAt.Format(time.RFC3339)
@@ -148,7 +148,7 @@ func (e *EntryConfig) LogicalResponseData() map[string]any {
148148
tokenCreatedAt = e.TokenCreatedAt.Format(time.RFC3339)
149149
}
150150

151-
return map[string]any{
151+
data = map[string]any{
152152
"base_url": e.BaseURL,
153153
"auto_rotate_token": e.AutoRotateToken,
154154
"auto_rotate_before": e.AutoRotateBefore.String(),
@@ -163,6 +163,12 @@ func (e *EntryConfig) LogicalResponseData() map[string]any {
163163
"type": e.Type.String(),
164164
"name": e.Name,
165165
}
166+
167+
if includeToken {
168+
data["token"] = e.Token
169+
}
170+
171+
return data
166172
}
167173

168174
func getConfig(ctx context.Context, s logical.Storage, name string) (cfg *EntryConfig, err error) {

flags.go

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package gitlab
2+
3+
import "flag"
4+
5+
type Flags struct {
6+
ShowConfigToken bool
7+
}
8+
9+
// FlagSet returns the flag set for configuring the TLS connection
10+
func (f *Flags) FlagSet(fs *flag.FlagSet) *flag.FlagSet {
11+
fs.BoolVar(&f.ShowConfigToken, "show-config-token", false, "")
12+
return fs
13+
}

flags_test.go

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package gitlab_test
2+
3+
import (
4+
"flag"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
9+
gitlab "github.com/ilijamt/vault-plugin-secrets-gitlab"
10+
)
11+
12+
func TestFlags_FlagSet(t *testing.T) {
13+
fs := flag.NewFlagSet("test", flag.ContinueOnError)
14+
15+
flags := &gitlab.Flags{}
16+
flags.FlagSet(fs)
17+
18+
assert.False(t, flags.ShowConfigToken)
19+
assert.NoError(t, fs.Parse([]string{"-show-config-token"}))
20+
assert.True(t, flags.ShowConfigToken)
21+
}

helpers_test.go

+5-1
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,10 @@ func (m *mockEventsSender) expectEvents(t *testing.T, expectedEvents []expectedE
9292
}
9393

9494
func getBackendWithEvents(ctx context.Context) (*gitlab.Backend, logical.Storage, *mockEventsSender, error) {
95+
return getBackendWithFlagsWithEvents(ctx, gitlab.Flags{})
96+
}
97+
98+
func getBackendWithFlagsWithEvents(ctx context.Context, flags gitlab.Flags) (*gitlab.Backend, logical.Storage, *mockEventsSender, error) {
9599
events := &mockEventsSender{}
96100
config := &logical.BackendConfig{
97101
Logger: logging.NewVaultLoggerWithWriter(io.Discard, log.NoLevel),
@@ -101,7 +105,7 @@ func getBackendWithEvents(ctx context.Context) (*gitlab.Backend, logical.Storage
101105
EventsSender: events,
102106
}
103107

104-
b, err := gitlab.Factory(ctx, config)
108+
b, err := gitlab.Factory(flags)(ctx, config)
105109
if err != nil {
106110
return nil, nil, nil, fmt.Errorf("unable to create Backend: %w", err)
107111
}

path_config.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -108,9 +108,9 @@ func (b *Backend) pathConfigRead(ctx context.Context, req *logical.Request, data
108108
if config == nil {
109109
return logical.ErrorResponse(ErrBackendNotConfigured.Error()), nil
110110
}
111-
lrd := config.LogicalResponseData()
111+
lrd := config.LogicalResponseData(b.flags.ShowConfigToken)
112112
b.Logger().Debug("Reading configuration info", "info", lrd)
113-
lResp = &logical.Response{Data: config.LogicalResponseData()}
113+
lResp = &logical.Response{Data: lrd}
114114
}
115115
return lResp, err
116116
}
@@ -142,7 +142,7 @@ func (b *Backend) pathConfigPatch(ctx context.Context, req *logical.Request, dat
142142
b.lockClientMutex.Lock()
143143
defer b.lockClientMutex.Unlock()
144144
if err = saveConfig(ctx, *config, req.Storage); err == nil {
145-
lrd := config.LogicalResponseData()
145+
lrd := config.LogicalResponseData(b.flags.ShowConfigToken)
146146
event(ctx, b.Backend, "config-patch", changes)
147147
b.SetClient(nil, name)
148148
b.Logger().Debug("Patched config", "lrd", lrd, "warnings", warnings)
@@ -214,7 +214,7 @@ func (b *Backend) pathConfigWrite(ctx context.Context, req *logical.Request, dat
214214
})
215215

216216
b.SetClient(nil, name)
217-
lrd := config.LogicalResponseData()
217+
lrd := config.LogicalResponseData(b.flags.ShowConfigToken)
218218
b.Logger().Debug("Wrote new config", "lrd", lrd, "warnings", warnings)
219219
lResp = &logical.Response{Data: lrd, Warnings: warnings}
220220
}

path_config_rotate.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ func (b *Backend) pathConfigTokenRotate(ctx context.Context, request *logical.Re
105105
return nil, err
106106
}
107107

108-
lResp = &logical.Response{Data: config.LogicalResponseData()}
108+
lResp = &logical.Response{Data: config.LogicalResponseData(b.flags.ShowConfigToken)}
109109
lResp.Data["token"] = config.Token
110110
event(ctx, b.Backend, "config-token-rotate", map[string]string{
111111
"path": fmt.Sprintf("%s/%s", PathConfigStorage, name),

path_config_test.go

+55
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,61 @@ func TestPathConfig(t *testing.T) {
102102
})
103103
})
104104

105+
t.Run("write, read, delete and read config with show config token", func(t *testing.T) {
106+
httpClient, url := getClient(t, "unit")
107+
ctx := gitlab.HttpClientNewContext(context.Background(), httpClient)
108+
109+
b, l, events, err := getBackendWithFlagsWithEvents(ctx, gitlab.Flags{ShowConfigToken: true})
110+
require.NoError(t, err)
111+
112+
resp, err := b.HandleRequest(ctx, &logical.Request{
113+
Operation: logical.UpdateOperation,
114+
Path: fmt.Sprintf("%s/%s", gitlab.PathConfigStorage, gitlab.DefaultConfigName), Storage: l,
115+
Data: map[string]any{
116+
"token": "glpat-secret-random-token",
117+
"base_url": url,
118+
"type": gitlab.TypeSelfManaged.String(),
119+
},
120+
})
121+
122+
require.NoError(t, err)
123+
require.NotNil(t, resp)
124+
require.NoError(t, resp.Error())
125+
126+
resp, err = b.HandleRequest(ctx, &logical.Request{
127+
Operation: logical.ReadOperation,
128+
Path: fmt.Sprintf("%s/%s", gitlab.PathConfigStorage, gitlab.DefaultConfigName), Storage: l,
129+
})
130+
131+
require.NoError(t, err)
132+
require.NotNil(t, resp)
133+
require.NoError(t, resp.Error())
134+
assert.NotEmpty(t, resp.Data["token_sha1_hash"])
135+
assert.NotEmpty(t, resp.Data["base_url"])
136+
require.Len(t, events.eventsProcessed, 1)
137+
require.NotEmpty(t, resp.Data["token"])
138+
139+
resp, err = b.HandleRequest(ctx, &logical.Request{
140+
Operation: logical.DeleteOperation,
141+
Path: fmt.Sprintf("%s/%s", gitlab.PathConfigStorage, gitlab.DefaultConfigName), Storage: l,
142+
})
143+
require.NoError(t, err)
144+
require.Nil(t, resp)
145+
require.Len(t, events.eventsProcessed, 2)
146+
147+
resp, err = b.HandleRequest(ctx, &logical.Request{
148+
Operation: logical.ReadOperation,
149+
Path: fmt.Sprintf("%s/%s", gitlab.PathConfigStorage, gitlab.DefaultConfigName), Storage: l,
150+
})
151+
require.NoError(t, err)
152+
require.NotNil(t, resp)
153+
require.Error(t, resp.Error())
154+
155+
events.expectEvents(t, []expectedEvent{
156+
{eventType: "gitlab/config-write"},
157+
{eventType: "gitlab/config-delete"},
158+
})
159+
})
105160
t.Run("invalid token", func(t *testing.T) {
106161
httpClient, url := getClient(t, "unit")
107162
ctx := gitlab.HttpClientNewContext(context.Background(), httpClient)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
---
2+
version: 2
3+
interactions:
4+
- id: 0
5+
request:
6+
proto: HTTP/1.1
7+
proto_major: 1
8+
proto_minor: 1
9+
content_length: 0
10+
transfer_encoding: []
11+
trailer: {}
12+
host: localhost:8080
13+
remote_addr: ""
14+
request_uri: ""
15+
body: ""
16+
form: {}
17+
headers:
18+
Accept:
19+
- application/json
20+
Private-Token:
21+
- REPLACED-TOKEN
22+
User-Agent:
23+
- go-gitlab
24+
url: http://localhost:8080/api/v4/personal_access_tokens/self
25+
method: GET
26+
response:
27+
proto: HTTP/1.1
28+
proto_major: 1
29+
proto_minor: 1
30+
transfer_encoding:
31+
- chunked
32+
trailer: {}
33+
content_length: -1
34+
uncompressed: true
35+
body: '{"id":1,"name":"Initial token","revoked":false,"created_at":"2024-07-11T18:53:26.792Z","scopes":["api","read_api","read_user","sudo","admin_mode","create_runner","k8s_proxy","read_repository","write_repository","ai_features","read_service_ping"],"user_id":1,"last_used_at":"2024-12-14T22:52:47.081Z","active":true,"expires_at":"2025-07-11"}'
36+
headers:
37+
Cache-Control:
38+
- max-age=0, private, must-revalidate
39+
Connection:
40+
- keep-alive
41+
Content-Type:
42+
- application/json
43+
Date:
44+
- Sat, 14 Dec 2024 22:52:51 GMT
45+
Etag:
46+
- W/"d0f486f8d8a3d398674f9cd0e555862f"
47+
Referrer-Policy:
48+
- strict-origin-when-cross-origin
49+
Server:
50+
- nginx
51+
Strict-Transport-Security:
52+
- max-age=63072000
53+
Vary:
54+
- Accept-Encoding
55+
- Origin
56+
X-Content-Type-Options:
57+
- nosniff
58+
X-Frame-Options:
59+
- SAMEORIGIN
60+
X-Gitlab-Meta:
61+
- '{"correlation_id":"01JF3NZSKYGZEE33KZ7H81F52E","version":"1"}'
62+
X-Request-Id:
63+
- 01JF3NZSKYGZEE33KZ7H81F52E
64+
X-Runtime:
65+
- "0.011814"
66+
status: 200 OK
67+
code: 200
68+
duration: 14.264125ms
69+
- id: 1
70+
request:
71+
proto: HTTP/1.1
72+
proto_major: 1
73+
proto_minor: 1
74+
content_length: 0
75+
transfer_encoding: []
76+
trailer: {}
77+
host: localhost:8080
78+
remote_addr: ""
79+
request_uri: ""
80+
body: ""
81+
form: {}
82+
headers:
83+
Accept:
84+
- application/json
85+
Private-Token:
86+
- REPLACED-TOKEN
87+
User-Agent:
88+
- go-gitlab
89+
url: http://localhost:8080/api/v4/metadata
90+
method: GET
91+
response:
92+
proto: HTTP/1.1
93+
proto_major: 1
94+
proto_minor: 1
95+
transfer_encoding: []
96+
trailer: {}
97+
content_length: 162
98+
uncompressed: false
99+
body: '{"version":"16.11.6","revision":"4684e042d0b","kas":{"enabled":true,"externalUrl":"ws://7b1d891ab6bb/-/kubernetes-agent/","version":"16.11.6"},"enterprise":false}'
100+
headers:
101+
Cache-Control:
102+
- max-age=0, private, must-revalidate
103+
Connection:
104+
- keep-alive
105+
Content-Length:
106+
- "162"
107+
Content-Type:
108+
- application/json
109+
Date:
110+
- Sat, 14 Dec 2024 22:52:51 GMT
111+
Etag:
112+
- W/"a29dcadce9c4771a1b7b66cc326f6617"
113+
Referrer-Policy:
114+
- strict-origin-when-cross-origin
115+
Server:
116+
- nginx
117+
Strict-Transport-Security:
118+
- max-age=63072000
119+
Vary:
120+
- Origin
121+
X-Content-Type-Options:
122+
- nosniff
123+
X-Frame-Options:
124+
- SAMEORIGIN
125+
X-Gitlab-Meta:
126+
- '{"correlation_id":"01JF3NZSMXEA3BW1J2RZN4AVKK","version":"1"}'
127+
X-Request-Id:
128+
- 01JF3NZSMXEA3BW1J2RZN4AVKK
129+
X-Runtime:
130+
- "0.017671"
131+
status: 200 OK
132+
code: 200
133+
duration: 20.426875ms

0 commit comments

Comments
 (0)