Skip to content

Commit

Permalink
add cross domain scoping (#1118)
Browse files Browse the repository at this point in the history
Signed-off-by: Arno Uhlig <[email protected]>
  • Loading branch information
auhlig authored and bwplotka committed May 30, 2019
1 parent 57f408d commit 69782ff
Showing 4 changed files with 150 additions and 37 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -21,6 +21,9 @@ We use *breaking* word for marking changes that are not backward compatible (rel

### Changed

- [#1118](https://github.com/improbable-eng/thanos/pull/1118) swift: Added support for cross-domain authentication by introducing `userDomainID`, `userDomainName`, `projectDomainID`, `projectDomainName`.
The outdated terms `tenantID`, `tenantName` are deprecated and have been replaced by `projectID`, `projectName`.

- [#1066](https://github.com/improbable-eng/thanos/pull/1066) Upgrade Thanos ui to Prometheus v2.9.1.

Changes from the upstream:
13 changes: 10 additions & 3 deletions docs/storage.md
Original file line number Diff line number Diff line change
@@ -260,20 +260,27 @@ config:
### OpenStack Swift Configuration
Thanos uses [gophercloud](http://gophercloud.io/) client to upload Prometheus data into [OpenStack Swift](https://docs.openstack.org/swift/latest/).

Below is an example configuration file for thanos to use OpenStack swift container as an object store.
Below is an example configuration file for thanos to use OpenStack swift container as an object store.
Note that if the `name` of a user, project or tenant is used one must also specify its domain by ID or name.
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).


[embedmd]:# (flags/config_swift.txt yaml)
```yaml
type: SWIFT
config:
auth_url: ""
username: ""
user_domain_name: ""
user_domain_id: ""
user_id: ""
password: ""
domain_id: ""
domain_name: ""
tenant_id: ""
tenant_name: ""
project_id: ""
project_name: ""
project_domain_id: ""
project_domain_name: ""
region_name: ""
container_name: ""
```
119 changes: 85 additions & 34 deletions pkg/objstore/swift/swift.go
Original file line number Diff line number Diff line change
@@ -11,14 +11,13 @@ import (
"testing"
"time"

"github.com/improbable-eng/thanos/pkg/objstore"

"github.com/go-kit/kit/log"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack"
"github.com/gophercloud/gophercloud/openstack/objectstorage/v1/containers"
"github.com/gophercloud/gophercloud/openstack/objectstorage/v1/objects"
"github.com/gophercloud/gophercloud/pagination"
"github.com/improbable-eng/thanos/pkg/objstore"
"github.com/pkg/errors"
yaml "gopkg.in/yaml.v2"
)
@@ -27,16 +26,20 @@ import (
const DirDelim = "/"

type SwiftConfig struct {
AuthUrl string `yaml:"auth_url"`
Username string `yaml:"username"`
UserId string `yaml:"user_id"`
Password string `yaml:"password"`
DomainId string `yaml:"domain_id"`
DomainName string `yaml:"domain_name"`
TenantID string `yaml:"tenant_id"`
TenantName string `yaml:"tenant_name"`
RegionName string `yaml:"region_name"`
ContainerName string `yaml:"container_name"`
AuthUrl string `yaml:"auth_url"`
Username string `yaml:"username"`
UserDomainName string `yaml:"user_domain_name"`
UserDomainID string `yaml:"user_domain_id"`
UserId string `yaml:"user_id"`
Password string `yaml:"password"`
DomainId string `yaml:"domain_id"`
DomainName string `yaml:"domain_name"`
ProjectID string `yaml:"project_id"`
ProjectName string `yaml:"project_name"`
ProjectDomainID string `yaml:"project_domain_id"`
ProjectDomainName string `yaml:"project_domain_name"`
RegionName string `yaml:"region_name"`
ContainerName string `yaml:"container_name"`
}

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

func NewContainer(logger log.Logger, conf []byte) (*Container, error) {
var sc SwiftConfig
if err := yaml.Unmarshal(conf, &sc); err != nil {
sc, err := parseConfig(conf)
if err != nil {
return nil, err
}

authOpts := gophercloud.AuthOptions{
IdentityEndpoint: sc.AuthUrl,
Username: sc.Username,
UserID: sc.UserId,
Password: sc.Password,
DomainID: sc.DomainId,
DomainName: sc.DomainName,
TenantID: sc.TenantID,
TenantName: sc.TenantName,

// Allow Gophercloud to re-authenticate automatically.
AllowReauth: true,
authOpts, err := authOptsFromConfig(sc)
if err != nil {
return nil, err
}

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

func parseConfig(conf []byte) (*SwiftConfig, error) {
var sc SwiftConfig
err := yaml.UnmarshalStrict(conf, &sc)
return &sc, err
}

func authOptsFromConfig(sc *SwiftConfig) (gophercloud.AuthOptions, error) {
authOpts := gophercloud.AuthOptions{
IdentityEndpoint: sc.AuthUrl,
Username: sc.Username,
UserID: sc.UserId,
Password: sc.Password,
DomainID: sc.DomainId,
DomainName: sc.DomainName,
TenantID: sc.ProjectID,
TenantName: sc.ProjectName,

// Allow Gophercloud to re-authenticate automatically.
AllowReauth: true,
}

// Support for cross-domain scoping (user in different domain than project).
// If a userDomainName or userDomainID is given, the user is scoped to this domain.
switch {
case sc.UserDomainName != "":
authOpts.DomainName = sc.UserDomainName
case sc.UserDomainID != "":
authOpts.DomainID = sc.UserDomainID
}

// A token can be scoped to a domain or project.
// The project can be in another domain than the user, which is indicated by setting either projectDomainName or projectDomainID.
switch {
case sc.ProjectDomainName != "":
authOpts.Scope = &gophercloud.AuthScope{
DomainName: sc.ProjectDomainName,
}
case sc.ProjectDomainID != "":
authOpts.Scope = &gophercloud.AuthScope{
DomainID: sc.ProjectDomainID,
}
}
if authOpts.Scope != nil {
switch {
case sc.ProjectName != "":
authOpts.Scope.ProjectName = sc.ProjectName
case sc.ProjectID != "":
authOpts.Scope.ProjectID = sc.ProjectID
}
}
return authOpts, nil
}

func (c *Container) createContainer(name string) error {
return containers.Create(c.client, name, nil).Err
}
@@ -180,13 +227,17 @@ func (c *Container) deleteContainer(name string) error {

func configFromEnv() SwiftConfig {
c := SwiftConfig{
AuthUrl: os.Getenv("OS_AUTH_URL"),
Username: os.Getenv("OS_USERNAME"),
Password: os.Getenv("OS_PASSWORD"),
TenantID: os.Getenv("OS_TENANT_ID"),
TenantName: os.Getenv("OS_TENANT_NAME"),
RegionName: os.Getenv("OS_REGION_NAME"),
ContainerName: os.Getenv("OS_CONTAINER_NAME"),
AuthUrl: os.Getenv("OS_AUTH_URL"),
Username: os.Getenv("OS_USERNAME"),
Password: os.Getenv("OS_PASSWORD"),
RegionName: os.Getenv("OS_REGION_NAME"),
ContainerName: os.Getenv("OS_CONTAINER_NAME"),
ProjectID: os.Getenv("OS_PROJECT_ID"),
ProjectName: os.Getenv("OS_PROJECT_NAME"),
UserDomainID: os.Getenv("OS_USER_DOMAIN_ID"),
UserDomainName: os.Getenv("OS_USER_DOMAIN_NAME"),
ProjectDomainID: os.Getenv("OS_PROJET_DOMAIN_ID"),
ProjectDomainName: os.Getenv("OS_PROJECT_DOMAIN_NAME"),
}

return c
@@ -197,7 +248,7 @@ func validateForTests(conf SwiftConfig) error {
if conf.AuthUrl == "" ||
conf.Username == "" ||
conf.Password == "" ||
(conf.TenantName == "" && conf.TenantID == "") ||
(conf.ProjectName == "" && conf.ProjectID == "") ||
conf.RegionName == "" {
return errors.New("insufficient swift test configuration information")
}
52 changes: 52 additions & 0 deletions pkg/objstore/swift/swift_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package swift

import (
"testing"

"github.com/improbable-eng/thanos/pkg/testutil"
)

func TestParseConfig(t *testing.T) {
input := []byte(`auth_url: http://identity.something.com/v3
username: thanos
user_domain_name: userDomain
project_name: thanosProject
project_domain_name: projectDomain`)

cfg, err := parseConfig(input)
testutil.Ok(t, err)

testutil.Equals(t, "http://identity.something.com/v3", cfg.AuthUrl)
testutil.Equals(t, "thanos", cfg.Username)
testutil.Equals(t, "userDomain", cfg.UserDomainName)
testutil.Equals(t, "thanosProject", cfg.ProjectName)
testutil.Equals(t, "projectDomain", cfg.ProjectDomainName)
}

func TestParseConfigFail(t *testing.T) {
input := []byte(`auth_url: http://identity.something.com/v3
tenant_name: something`)

_, err := parseConfig(input)
// Must result in unmarshal error as there's no `tenant_name` in SwiftConfig.
testutil.NotOk(t, err)
}

func TestAuthOptsFromConfig(t *testing.T) {
input := &SwiftConfig{
AuthUrl: "http://identity.something.com/v3",
Username: "thanos",
UserDomainName: "userDomain",
ProjectName: "thanosProject",
ProjectDomainName: "projectDomain",
}

authOpts, err := authOptsFromConfig(input)
testutil.Ok(t, err)

testutil.Equals(t, "http://identity.something.com/v3", authOpts.IdentityEndpoint)
testutil.Equals(t, "thanos", authOpts.Username)
testutil.Equals(t, "userDomain", authOpts.DomainName)
testutil.Equals(t, "projectDomain", authOpts.Scope.DomainName)
testutil.Equals(t, "thanosProject", authOpts.Scope.ProjectName)
}

0 comments on commit 69782ff

Please sign in to comment.