Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add cross domain scoping #1118

Merged
merged 1 commit into from
May 30, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,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:
Expand Down
13 changes: 10 additions & 3 deletions docs/storage.md
Original file line number Diff line number Diff line change
Expand Up @@ -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: ""
```
Expand Down
119 changes: 85 additions & 34 deletions pkg/objstore/swift/swift.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand All @@ -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 {
Expand All @@ -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)
Expand Down Expand Up @@ -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
}
Expand All @@ -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
Expand All @@ -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")
}
Expand Down
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)
}