Skip to content
This repository was archived by the owner on Apr 2, 2024. It is now read-only.

Commit b6c4e6c

Browse files
committed
Move auth functionality into separate package
Signed-off-by: Arunprasad Rajkumar <[email protected]>
1 parent ad4f88a commit b6c4e6c

14 files changed

+503
-483
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ We use the following categories for changes:
1818
reader and writer [#1020].
1919
- Run timescaledb-tune with the promscale profile [#1615]
2020
- Propagate the context from received HTTP read requests downstream to database
21-
requests [#1205].
21+
requests [#1205]
2222
- Add cmd flag `web.auth.ignore-path` to skip http paths from authentication [#1637]
2323

2424
### Changed

docs/configuration.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ The following subsections cover all CLI flags which promscale supports. You can
154154
| web.auth.password | string | "" | Authentication password used for web endpoint authentication. This flag should be set together with auth-username. It is mutually exclusive with auth-password-file and bearer-token methods. |
155155
| web.auth.password-file | string | "" | Path for auth password file containing the actual password used for web endpoint authentication. This flag should be set together with auth-username. It is mutually exclusive with auth-password and bearer-token methods. |
156156
| web.auth.username | string | "" | Authentication username used for web endpoint authentication. Disabled by default. |
157-
| web.auth.ignore-path | string | "" | Http paths which has to be skipped from authentication. This flag shall be repeated and each one would be appended to the ignore list. |
157+
| web.auth.ignore-path | string | "" | HTTP paths which has to be skipped from authentication. This flag shall be repeated and each one would be appended to the ignore list. |
158158
| web.cors-origin | string | `.*` | Regex for CORS origin. It is fully anchored. Example: 'https?://(domain1 |
159159
| web.enable-admin-api | boolean | false | Allow operations via API that are for advanced users. Currently, these operations are limited to deletion of series. |
160160
| web.listen-address | string | `:9201` | Address to listen on for web endpoints. |

pkg/api/common.go

Lines changed: 1 addition & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,7 @@ import (
1111
"io"
1212
"math"
1313
"net/http"
14-
"os"
15-
"path"
1614
"strconv"
17-
"strings"
1815
"time"
1916

2017
"github.com/grafana/regexp"
@@ -33,121 +30,30 @@ import (
3330
var (
3431
minTimeFormatted = pgmodel.MinTime.Format(time.RFC3339Nano)
3532
maxTimeFormatted = pgmodel.MaxTime.Format(time.RFC3339Nano)
36-
37-
usernameAndTokenFlagsSetError = fmt.Errorf("at most one of basic-auth-username, bearer-token & bearer-token-file must be set")
38-
noUsernameFlagSetError = fmt.Errorf("invalid auth setup, cannot enable authorization with password only (username required)")
39-
noPasswordFlagsSetError = fmt.Errorf("one of basic-auth-password & basic-auth-password-file must be configured")
40-
multiplePasswordFlagsSetError = fmt.Errorf("at most one of basic-auth-password & basic-auth-password-file must be configured")
41-
multipleTokenFlagsSetError = fmt.Errorf("at most one of bearer-token & bearer-token-file must be set")
4233
)
4334

44-
type arrayOfIgnorePaths []string
45-
46-
type Auth struct {
47-
BasicAuthUsername string
48-
BasicAuthPassword string
49-
BasicAuthPasswordFile string
50-
51-
BearerToken string
52-
BearerTokenFile string
53-
54-
IgnorePaths arrayOfIgnorePaths
55-
}
56-
57-
func (p *arrayOfIgnorePaths) String() string {
58-
return fmt.Sprintf("auth ignored paths: %#v", p)
59-
}
60-
61-
func (p *arrayOfIgnorePaths) Set(path string) error {
62-
*p = append(*p, path)
63-
return nil
64-
}
65-
66-
func (a *Auth) Validate() error {
67-
switch {
68-
case a.BasicAuthUsername != "":
69-
if a.BearerToken != "" || a.BearerTokenFile != "" {
70-
return usernameAndTokenFlagsSetError
71-
}
72-
if a.BasicAuthPassword == "" && a.BasicAuthPasswordFile == "" {
73-
return noPasswordFlagsSetError
74-
}
75-
if a.BasicAuthPassword != "" && a.BasicAuthPasswordFile != "" {
76-
return multiplePasswordFlagsSetError
77-
}
78-
pwd, err := readFromFile(a.BasicAuthPasswordFile, a.BasicAuthPassword)
79-
if err != nil {
80-
return fmt.Errorf("error reading password file: %w", err)
81-
}
82-
a.BasicAuthPassword = pwd
83-
case a.BasicAuthPassword != "" || a.BasicAuthPasswordFile != "":
84-
// At this point, if we have password set with no username, throw
85-
// error to warn the user this is an invalid auth setup.
86-
return noUsernameFlagSetError
87-
case a.BearerToken != "" || a.BearerTokenFile != "":
88-
if a.BearerToken != "" && a.BearerTokenFile != "" {
89-
return multipleTokenFlagsSetError
90-
}
91-
token, err := readFromFile(a.BearerTokenFile, a.BearerToken)
92-
if err != nil {
93-
return fmt.Errorf("error reading bearer token file: %w", err)
94-
}
95-
a.BearerToken = token
96-
case a.IgnorePaths != nil:
97-
for _, p := range a.IgnorePaths {
98-
_, err := path.Match(p, "")
99-
if err != nil {
100-
return fmt.Errorf("invalid ignore path pattern: %w", err)
101-
}
102-
}
103-
}
104-
105-
return nil
106-
}
107-
10835
type Config struct {
10936
AllowedOrigin *regexp.Regexp
11037
ReadOnly bool
11138
HighAvailability bool
11239
AdminAPIEnabled bool
11340
TelemetryPath string
11441

115-
Auth *Auth
11642
MultiTenancy tenancy.Authorizer
11743
Rules *rules.Manager
11844
}
11945

12046
func ParseFlags(fs *flag.FlagSet, cfg *Config) *Config {
121-
cfg.Auth = &Auth{}
122-
12347
fs.BoolVar(&cfg.ReadOnly, "db.read-only", false, "Read-only mode for the connector. Operations related to writing or updating the database are disallowed. It is used when pointing the connector to a TimescaleDB read replica.")
12448
fs.BoolVar(&cfg.HighAvailability, "metrics.high-availability", false, "Enable external_labels based HA.")
12549
fs.BoolVar(&cfg.AdminAPIEnabled, "web.enable-admin-api", false, "Allow operations via API that are for advanced users. Currently, these operations are limited to deletion of series.")
12650
fs.StringVar(&cfg.TelemetryPath, "web.telemetry-path", "/metrics", "Web endpoint for exposing Promscale's Prometheus metrics.")
12751

128-
fs.StringVar(&cfg.Auth.BasicAuthUsername, "web.auth.username", "", "Authentication username used for web endpoint authentication. Disabled by default.")
129-
fs.StringVar(&cfg.Auth.BasicAuthPassword, "web.auth.password", "", "Authentication password used for web endpoint authentication. This flag should be set together with auth-username. It is mutually exclusive with auth-password-file and bearer-token flags.")
130-
fs.StringVar(&cfg.Auth.BasicAuthPasswordFile, "web.auth.password-file", "", "Path for auth password file containing the actual password used for web endpoint authentication. This flag should be set together with auth-username. It is mutually exclusive with auth-password and bearer-token methods.")
131-
fs.StringVar(&cfg.Auth.BearerToken, "web.auth.bearer-token", "", "Bearer token (JWT) used for web endpoint authentication. Disabled by default. Mutually exclusive with bearer-token-file and basic auth methods.")
132-
fs.StringVar(&cfg.Auth.BearerTokenFile, "web.auth.bearer-token-file", "", "Path of the file containing the bearer token (JWT) used for web endpoint authentication. Disabled by default. Mutually exclusive with bearer-token and basic auth methods.")
133-
fs.Var(&cfg.Auth.IgnorePaths, "web.auth.ignore-path", "Http paths which has to be skipped from authentication. This flag shall be repeated and each one would be appended to the ignore list.")
13452
return cfg
13553
}
13654

13755
func Validate(cfg *Config) error {
138-
return cfg.Auth.Validate()
139-
}
140-
141-
func readFromFile(path string, defaultValue string) (string, error) {
142-
if path == "" {
143-
return defaultValue, nil
144-
}
145-
bs, err := os.ReadFile(path) // #nosec G304
146-
if err != nil {
147-
return "", fmt.Errorf("unable to read file %s: %w", path, err)
148-
}
149-
150-
return strings.TrimSpace(string(bs)), nil
56+
return nil
15157
}
15258

15359
func corsWrapper(conf *Config, f http.HandlerFunc) http.HandlerFunc {

pkg/api/common_test.go

Lines changed: 0 additions & 157 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,8 @@ package api
66

77
import (
88
"context"
9-
"errors"
109
"net/http"
1110
"net/http/httptest"
12-
"os"
13-
"path"
1411
"reflect"
1512
"strings"
1613
"testing"
@@ -122,160 +119,6 @@ func doCORSWrapperRequest(t *testing.T, queryHandler http.Handler, url, origin s
122119
return w
123120
}
124121

125-
func TestValidateConfig(t *testing.T) {
126-
fileContents, err := os.ReadFile("common_test.go")
127-
if err != nil {
128-
t.Fatal("error reading file contents common_test.go")
129-
}
130-
fileContentsString := strings.TrimSpace(string(fileContents))
131-
testCases := []struct {
132-
name string
133-
cfg *Auth
134-
returnErr error
135-
passSet string
136-
tokenSet string
137-
}{
138-
{
139-
name: "empty config",
140-
cfg: &Auth{},
141-
},
142-
{
143-
name: "basic auth and bearer token set",
144-
cfg: &Auth{
145-
BasicAuthUsername: "foo",
146-
BearerToken: "foo",
147-
},
148-
returnErr: usernameAndTokenFlagsSetError,
149-
},
150-
{
151-
name: "basic auth missing password",
152-
cfg: &Auth{
153-
BasicAuthUsername: "foo",
154-
},
155-
returnErr: noPasswordFlagsSetError,
156-
},
157-
{
158-
name: "basic auth password and password file set",
159-
cfg: &Auth{
160-
BasicAuthUsername: "foo",
161-
BasicAuthPassword: "foo",
162-
BasicAuthPasswordFile: "foo",
163-
},
164-
returnErr: multiplePasswordFlagsSetError,
165-
},
166-
{
167-
name: "basic auth invalid password file",
168-
cfg: &Auth{
169-
BasicAuthUsername: "foo",
170-
BasicAuthPasswordFile: "invalid filename",
171-
},
172-
returnErr: os.ErrNotExist,
173-
},
174-
{
175-
name: "basic auth password set",
176-
cfg: &Auth{
177-
BasicAuthUsername: "foo",
178-
BasicAuthPassword: "pass",
179-
},
180-
passSet: "pass",
181-
},
182-
{
183-
name: "basic auth no username set",
184-
cfg: &Auth{
185-
BasicAuthPassword: "pass",
186-
},
187-
returnErr: noUsernameFlagSetError,
188-
},
189-
{
190-
name: "basic auth password file set",
191-
cfg: &Auth{
192-
BasicAuthUsername: "foo",
193-
BasicAuthPasswordFile: "common_test.go",
194-
},
195-
passSet: fileContentsString,
196-
},
197-
{
198-
name: "bearer token and token file set",
199-
cfg: &Auth{
200-
BearerToken: "foo",
201-
BearerTokenFile: "foo",
202-
},
203-
returnErr: multipleTokenFlagsSetError,
204-
},
205-
{
206-
name: "bearer token set",
207-
cfg: &Auth{
208-
BearerToken: "foo",
209-
},
210-
tokenSet: "foo",
211-
},
212-
{
213-
name: "bearer token file set",
214-
cfg: &Auth{
215-
BearerTokenFile: "common_test.go",
216-
},
217-
tokenSet: fileContentsString,
218-
},
219-
{
220-
name: "bearer token file invalid file set",
221-
cfg: &Auth{
222-
BearerTokenFile: "invalid file",
223-
},
224-
returnErr: os.ErrNotExist,
225-
},
226-
{
227-
name: "all config options set",
228-
cfg: &Auth{
229-
BasicAuthUsername: "set",
230-
BasicAuthPassword: "set",
231-
BasicAuthPasswordFile: "set",
232-
BearerToken: "set",
233-
BearerTokenFile: "set",
234-
},
235-
returnErr: usernameAndTokenFlagsSetError,
236-
},
237-
{
238-
name: "invalid ignore path",
239-
cfg: &Auth{
240-
IgnorePaths: []string{
241-
"[",
242-
},
243-
},
244-
returnErr: path.ErrBadPattern,
245-
},
246-
{
247-
name: "valid ignore path",
248-
cfg: &Auth{
249-
IgnorePaths: []string{
250-
"/hello",
251-
},
252-
},
253-
},
254-
}
255-
256-
for _, c := range testCases {
257-
t.Run(c.name, func(t *testing.T) {
258-
err := Validate(&Config{
259-
Auth: c.cfg,
260-
})
261-
if c.returnErr != nil {
262-
if !errors.Is(err, c.returnErr) {
263-
t.Errorf("unexpected error received: %s", err)
264-
}
265-
} else if err != nil {
266-
t.Errorf("unexpected error received: %s", err)
267-
}
268-
269-
if c.passSet != "" && c.cfg.BasicAuthPassword != c.passSet {
270-
t.Errorf("unexpected password set: got %s wanted %s", c.cfg.BasicAuthPassword, c.passSet)
271-
}
272-
if c.tokenSet != "" && c.cfg.BearerToken != c.tokenSet {
273-
t.Errorf("unexpected bearer token set: got %s wanted %s", c.cfg.BearerToken, c.tokenSet)
274-
}
275-
})
276-
}
277-
}
278-
279122
func TestMarshalExemplar(t *testing.T) {
280123
tcs := []struct {
281124
name string

0 commit comments

Comments
 (0)