Skip to content

Commit a680c93

Browse files
committed
add cross domain scoping
Signed-off-by: Arno Uhlig <arno.uhlig@sap.com>
1 parent af78279 commit a680c93

File tree

4 files changed

+151
-37
lines changed

4 files changed

+151
-37
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ We use *breaking* word for marking changes that are not backward compatible (rel
1111

1212
## Unreleased
1313

14+
### Added
15+
16+
- [#1118](https://github.com/improbable-eng/thanos/pull/1118) swift: Added support for cross-domain authentication by introducing `userDomainID`, `userDomainName`, `projectDomainID`, `projectDomainName`. Mark `tenantID`, `tenantName` as deprecated and encourage usage of `projectID`, `projectName`.
17+
1418
## [v0.4.0](https://github.com/improbable-eng/thanos/releases/tag/v0.4.0) - 2019.05.3
1519

1620
:warning: **IMPORTANT** :warning: This is the last release that supports gossip. From Thanos v0.5.0, gossip will be completely removed.

docs/storage.md

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -252,20 +252,27 @@ config:
252252
### OpenStack Swift Configuration
253253
Thanos uses [gophercloud](http://gophercloud.io/) client to upload Prometheus data into [OpenStack Swift](https://docs.openstack.org/swift/latest/).
254254

255-
Below is an example configuration file for thanos to use OpenStack swift container as an object store.
255+
Below is an example configuration file for thanos to use OpenStack swift container as an object store.
256+
Note that if the `name` of a user, project or tenant is used one must also specify its domain by ID or name.
257+
Various examples for OpenStack authentication can be found in the [official documentation](https://developer.openstack.org/api-ref/identity/v3/index.html?expanded=password-authentication-with-scoped-authorization-detail#password-authentication-with-unscoped-authorization).
258+
256259

257260
[embedmd]:# (flags/config_swift.txt yaml)
258261
```yaml
259262
type: SWIFT
260263
config:
261264
auth_url: ""
262265
username: ""
266+
user_domain_name: ""
267+
user_domain_id: ""
263268
user_id: ""
264269
password: ""
265270
domain_id: ""
266271
domain_name: ""
267-
tenant_id: ""
268-
tenant_name: ""
272+
project_id: ""
273+
project_name: ""
274+
project_domain_id: ""
275+
project_domain_name: ""
269276
region_name: ""
270277
container_name: ""
271278
```

pkg/objstore/swift/swift.go

Lines changed: 85 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,13 @@ import (
1111
"testing"
1212
"time"
1313

14-
"github.com/improbable-eng/thanos/pkg/objstore"
15-
1614
"github.com/go-kit/kit/log"
1715
"github.com/gophercloud/gophercloud"
1816
"github.com/gophercloud/gophercloud/openstack"
1917
"github.com/gophercloud/gophercloud/openstack/objectstorage/v1/containers"
2018
"github.com/gophercloud/gophercloud/openstack/objectstorage/v1/objects"
2119
"github.com/gophercloud/gophercloud/pagination"
20+
"github.com/improbable-eng/thanos/pkg/objstore"
2221
"github.com/pkg/errors"
2322
"gopkg.in/yaml.v2"
2423
)
@@ -27,16 +26,20 @@ import (
2726
const DirDelim = "/"
2827

2928
type SwiftConfig struct {
30-
AuthUrl string `yaml:"auth_url"`
31-
Username string `yaml:"username"`
32-
UserId string `yaml:"user_id"`
33-
Password string `yaml:"password"`
34-
DomainId string `yaml:"domain_id"`
35-
DomainName string `yaml:"domain_name"`
36-
TenantID string `yaml:"tenant_id"`
37-
TenantName string `yaml:"tenant_name"`
38-
RegionName string `yaml:"region_name"`
39-
ContainerName string `yaml:"container_name"`
29+
AuthUrl string `yaml:"auth_url"`
30+
Username string `yaml:"username"`
31+
UserDomainName string `yaml:"user_domain_name"`
32+
UserDomainID string `yaml:"user_domain_id"`
33+
UserId string `yaml:"user_id"`
34+
Password string `yaml:"password"`
35+
DomainId string `yaml:"domain_id"`
36+
DomainName string `yaml:"domain_name"`
37+
ProjectID string `yaml:"project_id"`
38+
ProjectName string `yaml:"project_name"`
39+
ProjectDomainID string `yaml:"project_domain_id"`
40+
ProjectDomainName string `yaml:"project_domain_name"`
41+
RegionName string `yaml:"region_name"`
42+
ContainerName string `yaml:"container_name"`
4043
}
4144

4245
type Container struct {
@@ -46,23 +49,14 @@ type Container struct {
4649
}
4750

4851
func NewContainer(logger log.Logger, conf []byte) (*Container, error) {
49-
var sc SwiftConfig
50-
if err := yaml.Unmarshal(conf, &sc); err != nil {
52+
sc, err := parseConfig(conf)
53+
if err != nil {
5154
return nil, err
5255
}
5356

54-
authOpts := gophercloud.AuthOptions{
55-
IdentityEndpoint: sc.AuthUrl,
56-
Username: sc.Username,
57-
UserID: sc.UserId,
58-
Password: sc.Password,
59-
DomainID: sc.DomainId,
60-
DomainName: sc.DomainName,
61-
TenantID: sc.TenantID,
62-
TenantName: sc.TenantName,
63-
64-
// Allow Gophercloud to re-authenticate automatically.
65-
AllowReauth: true,
57+
authOpts, err := authOptsFromConfig(sc)
58+
if err != nil {
59+
return nil, err
6660
}
6761

6862
provider, err := openstack.AuthenticatedClient(authOpts)
@@ -170,6 +164,59 @@ func (*Container) Close() error {
170164
return nil
171165
}
172166

167+
func parseConfig(conf []byte) (*SwiftConfig, error) {
168+
var sc SwiftConfig
169+
err := yaml.UnmarshalStrict(conf, &sc)
170+
return &sc, err
171+
}
172+
173+
func authOptsFromConfig(sc *SwiftConfig) (gophercloud.AuthOptions, error) {
174+
authOpts := gophercloud.AuthOptions{
175+
IdentityEndpoint: sc.AuthUrl,
176+
Username: sc.Username,
177+
UserID: sc.UserId,
178+
Password: sc.Password,
179+
DomainID: sc.DomainId,
180+
DomainName: sc.DomainName,
181+
TenantID: sc.ProjectID,
182+
TenantName: sc.ProjectName,
183+
184+
// Allow Gophercloud to re-authenticate automatically.
185+
AllowReauth: true,
186+
}
187+
188+
// Support for cross-domain scoping (user in different domain than project).
189+
// If a userDomainName or userDomainID is given, the user is scoped to this domain.
190+
switch {
191+
case sc.UserDomainName != "":
192+
authOpts.DomainName = sc.UserDomainName
193+
case sc.UserDomainID != "":
194+
authOpts.DomainID = sc.UserDomainID
195+
}
196+
197+
// A token can be scoped to a domain or project.
198+
// The project can be in another domain than the user, which is indicated by setting either projectDomainName or projectDomainID.
199+
switch {
200+
case sc.ProjectDomainName != "":
201+
authOpts.Scope = &gophercloud.AuthScope{
202+
DomainName: sc.ProjectDomainName,
203+
}
204+
case sc.ProjectDomainID != "":
205+
authOpts.Scope = &gophercloud.AuthScope{
206+
DomainID: sc.ProjectDomainID,
207+
}
208+
}
209+
if authOpts.Scope != nil {
210+
switch {
211+
case sc.ProjectName != "":
212+
authOpts.Scope.ProjectName = sc.ProjectName
213+
case sc.ProjectID != "":
214+
authOpts.Scope.ProjectID = sc.ProjectID
215+
}
216+
}
217+
return authOpts, nil
218+
}
219+
173220
func (c *Container) createContainer(name string) error {
174221
return containers.Create(c.client, name, nil).Err
175222
}
@@ -180,13 +227,17 @@ func (c *Container) deleteContainer(name string) error {
180227

181228
func configFromEnv() SwiftConfig {
182229
c := SwiftConfig{
183-
AuthUrl: os.Getenv("OS_AUTH_URL"),
184-
Username: os.Getenv("OS_USERNAME"),
185-
Password: os.Getenv("OS_PASSWORD"),
186-
TenantID: os.Getenv("OS_TENANT_ID"),
187-
TenantName: os.Getenv("OS_TENANT_NAME"),
188-
RegionName: os.Getenv("OS_REGION_NAME"),
189-
ContainerName: os.Getenv("OS_CONTAINER_NAME"),
230+
AuthUrl: os.Getenv("OS_AUTH_URL"),
231+
Username: os.Getenv("OS_USERNAME"),
232+
Password: os.Getenv("OS_PASSWORD"),
233+
RegionName: os.Getenv("OS_REGION_NAME"),
234+
ContainerName: os.Getenv("OS_CONTAINER_NAME"),
235+
ProjectID: os.Getenv("OS_PROJECT_ID"),
236+
ProjectName: os.Getenv("OS_PROJECT_NAME"),
237+
UserDomainID: os.Getenv("OS_USER_DOMAIN_ID"),
238+
UserDomainName: os.Getenv("OS_USER_DOMAIN_NAME"),
239+
ProjectDomainID: os.Getenv("OS_PROJET_DOMAIN_ID"),
240+
ProjectDomainName: os.Getenv("OS_PROJECT_DOMAIN_NAME"),
190241
}
191242

192243
return c
@@ -197,7 +248,7 @@ func validateForTests(conf SwiftConfig) error {
197248
if conf.AuthUrl == "" ||
198249
conf.Username == "" ||
199250
conf.Password == "" ||
200-
(conf.TenantName == "" && conf.TenantID == "") ||
251+
(conf.ProjectName == "" && conf.ProjectID == "") ||
201252
conf.RegionName == "" {
202253
return errors.New("insufficient swift test configuration information")
203254
}

pkg/objstore/swift/swift_test.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package swift
2+
3+
import (
4+
"testing"
5+
6+
"github.com/improbable-eng/thanos/pkg/testutil"
7+
)
8+
9+
func TestParseConfig(t *testing.T) {
10+
input := []byte(`auth_url: http://identity.something.com/v3
11+
username: thanos
12+
user_domain_name: userDomain
13+
project_name: thanosProject
14+
project_domain_name: projectDomain`)
15+
16+
cfg, err := parseConfig(input)
17+
testutil.Ok(t, err)
18+
19+
testutil.Equals(t, "http://identity.something.com/v3", cfg.AuthUrl)
20+
testutil.Equals(t, "thanos", cfg.Username)
21+
testutil.Equals(t, "userDomain", cfg.UserDomainName)
22+
testutil.Equals(t, "thanosProject", cfg.ProjectName)
23+
testutil.Equals(t, "projectDomain", cfg.ProjectDomainName)
24+
}
25+
26+
func TestParseConfigFail(t *testing.T) {
27+
input := []byte(`auth_url: http://identity.something.com/v3
28+
tenant_name: something`)
29+
30+
_, err := parseConfig(input)
31+
// Must result in unmarshal error as there's no `tenant_name` in SwiftConfig.
32+
testutil.NotOk(t, err)
33+
}
34+
35+
func TestAuthOptsFromConfig(t *testing.T) {
36+
input := &SwiftConfig{
37+
AuthUrl: "http://identity.something.com/v3",
38+
Username: "thanos",
39+
UserDomainName: "userDomain",
40+
ProjectName: "thanosProject",
41+
ProjectDomainName: "projectDomain",
42+
}
43+
44+
authOpts, err := authOptsFromConfig(input)
45+
testutil.Ok(t, err)
46+
47+
testutil.Equals(t, "http://identity.something.com/v3", authOpts.IdentityEndpoint)
48+
testutil.Equals(t, "thanos", authOpts.Username)
49+
testutil.Equals(t, "userDomain", authOpts.DomainName)
50+
testutil.Equals(t, "projectDomain", authOpts.Scope.DomainName)
51+
testutil.Equals(t, "thanosProject", authOpts.Scope.ProjectName)
52+
}

0 commit comments

Comments
 (0)