Commit 71834443 authored by Kevin Bulebush's avatar Kevin Bulebush Committed by Brian Brazil
Browse files

openstack_sd: Supporting application credential for authentication. (#4968)



* openstack_sd: Support application credentials for authentication.
Updated gophercloud
Signed-off-by: default avatarKevin Bulebush <kmbulebu@gmail.com>
parent b8ede997
......@@ -50,20 +50,23 @@ var (
// SDConfig is the configuration for OpenStack based service discovery.
type SDConfig struct {
IdentityEndpoint string `yaml:"identity_endpoint"`
Username string `yaml:"username"`
UserID string `yaml:"userid"`
Password config_util.Secret `yaml:"password"`
ProjectName string `yaml:"project_name"`
ProjectID string `yaml:"project_id"`
DomainName string `yaml:"domain_name"`
DomainID string `yaml:"domain_id"`
Role Role `yaml:"role"`
Region string `yaml:"region"`
RefreshInterval model.Duration `yaml:"refresh_interval,omitempty"`
Port int `yaml:"port"`
AllTenants bool `yaml:"all_tenants,omitempty"`
TLSConfig config_util.TLSConfig `yaml:"tls_config,omitempty"`
IdentityEndpoint string `yaml:"identity_endpoint"`
Username string `yaml:"username"`
UserID string `yaml:"userid"`
Password config_util.Secret `yaml:"password"`
ProjectName string `yaml:"project_name"`
ProjectID string `yaml:"project_id"`
DomainName string `yaml:"domain_name"`
DomainID string `yaml:"domain_id"`
ApplicationCredentialName string `yaml:"application_credential_name"`
ApplicationCredentialID string `yaml:"application_credential_id"`
ApplicationCredentialSecret config_util.Secret `yaml:"application_credential_secret"`
Role Role `yaml:"role"`
Region string `yaml:"region"`
RefreshInterval model.Duration `yaml:"refresh_interval,omitempty"`
Port int `yaml:"port"`
AllTenants bool `yaml:"all_tenants,omitempty"`
TLSConfig config_util.TLSConfig `yaml:"tls_config,omitempty"`
}
// OpenStackRole is role of the target in OpenStack.
......@@ -132,14 +135,17 @@ func NewDiscovery(conf *SDConfig, l log.Logger) (Discovery, error) {
}
} else {
opts = gophercloud.AuthOptions{
IdentityEndpoint: conf.IdentityEndpoint,
Username: conf.Username,
UserID: conf.UserID,
Password: string(conf.Password),
TenantName: conf.ProjectName,
TenantID: conf.ProjectID,
DomainName: conf.DomainName,
DomainID: conf.DomainID,
IdentityEndpoint: conf.IdentityEndpoint,
Username: conf.Username,
UserID: conf.UserID,
Password: string(conf.Password),
TenantName: conf.ProjectName,
TenantID: conf.ProjectID,
DomainName: conf.DomainName,
DomainID: conf.DomainID,
ApplicationCredentialID: conf.ApplicationCredentialID,
ApplicationCredentialName: conf.ApplicationCredentialName,
ApplicationCredentialSecret: string(conf.ApplicationCredentialSecret),
}
}
client, err := openstack.NewClient(opts.IdentityEndpoint)
......
......@@ -540,6 +540,17 @@ region: <string>
[ project_name: <string> ]
[ project_id: <string> ]
# The application_credential_id or application_credential_name fields are
# required if using an application credential to authenticate. Some providers
# allow you to create an application credential to authenticate rather than a
# password.
[ application_credential_name: <string> ]
[ application_credential_id: <string> ]
# The application_credential_secret field is required if using an application
# credential to authenticate.
[ application_credential_secret: <secret> ]
# Whether the service discovery should list all instances for all projects.
# It is only relevant for the 'instance' role and usually requires admin permissions.
[ all_tenants: <boolean> | default: false ]
......@@ -978,7 +989,7 @@ endpoint: <string>
# A list of groups for which targets are retrieved. If omitted, all containers
# available to the requesting account are scraped.
groups:
groups:
[ - <string> ... ]
# The port to use for discovery and metric scraping.
......
......@@ -37,7 +37,7 @@ require (
github.com/google/gofuzz v0.0.0-20150304233714-bbcb9da2d746 // indirect
github.com/google/pprof v0.0.0-20180605153948-8b03ce837f34
github.com/googleapis/gnostic v0.0.0-20180520015035-48a0ecefe2e4 // indirect
github.com/gophercloud/gophercloud v0.0.0-20170607034829-caf34a65f602
github.com/gophercloud/gophercloud v0.0.0-20181206160319-9d88c34913a9
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 // indirect
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 // indirect
github.com/grpc-ecosystem/grpc-gateway v0.0.0-20171126203511-e4b8a938efae
......
......@@ -87,8 +87,8 @@ github.com/google/pprof v0.0.0-20180605153948-8b03ce837f34 h1:mGdRet4qWdrDnNidFr
github.com/google/pprof v0.0.0-20180605153948-8b03ce837f34/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/googleapis/gnostic v0.0.0-20180520015035-48a0ecefe2e4 h1:yxHFSapGMUoyn+3v6LiJJxoJhvbDqIq8me0gAWehnSU=
github.com/googleapis/gnostic v0.0.0-20180520015035-48a0ecefe2e4/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
github.com/gophercloud/gophercloud v0.0.0-20170607034829-caf34a65f602 h1:Acc1d6mIuURCyYN6nkm1d7+Gycfq1+jUWdnBbTyGb6E=
github.com/gophercloud/gophercloud v0.0.0-20170607034829-caf34a65f602/go.mod h1:3WdhXV3rUYy9p6AUW8d94kr+HS62Y4VL9mBnFxsD8q4=
github.com/gophercloud/gophercloud v0.0.0-20181206160319-9d88c34913a9 h1:7TRGugCPfA2Mll6QT7cbhD1GXZwk7+1PUz8tYrOWXgQ=
github.com/gophercloud/gophercloud v0.0.0-20181206160319-9d88c34913a9/go.mod h1:3WdhXV3rUYy9p6AUW8d94kr+HS62Y4VL9mBnFxsD8q4=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM=
......
......@@ -7,11 +7,13 @@ install:
- go get github.com/mattn/goveralls
- go get golang.org/x/tools/cmd/goimports
go:
- 1.8
- tip
- "1.10"
- "tip"
env:
global:
- secure: "xSQsAG5wlL9emjbCdxzz/hYQsSpJ/bABO1kkbwMSISVcJ3Nk0u4ywF+LS4bgeOnwPfmFvNTOqVDu3RwEvMeWXSI76t1piCPcObutb2faKLVD/hLoAS76gYX+Z8yGWGHrSB7Do5vTPj1ERe2UljdrnsSeOXzoDwFxYRaZLX4bBOB4AyoGvRniil5QXPATiA1tsWX1VMicj8a4F8X+xeESzjt1Q5Iy31e7vkptu71bhvXCaoo5QhYwT+pLR9dN0S1b7Ro0KVvkRefmr1lUOSYd2e74h6Lc34tC1h3uYZCS4h47t7v5cOXvMNxinEj2C51RvbjvZI1RLVdkuAEJD1Iz4+Ote46nXbZ//6XRZMZz/YxQ13l7ux1PFjgEB6HAapmF5Xd8PRsgeTU9LRJxpiTJ3P5QJ3leS1va8qnziM5kYipj/Rn+V8g2ad/rgkRox9LSiR9VYZD2Pe45YCb1mTKSl2aIJnV7nkOqsShY5LNB4JZSg7xIffA+9YVDktw8dJlATjZqt7WvJJ49g6A61mIUV4C15q2JPGKTkZzDiG81NtmS7hFa7k0yaE2ELgYocbcuyUcAahhxntYTC0i23nJmEHVNiZmBO3u7EgpWe4KGVfumU+lt12tIn5b3dZRBBUk3QakKKozSK1QPHGpk/AZGrhu7H6l8to6IICKWtDcyMPQ="
before_script:
- go vet ./...
script:
- ./script/coverage
- ./script/format
......
- job:
name: gophercloud-unittest
parent: golang-test
description: |
Run gophercloud unit test
run: .zuul/playbooks/gophercloud-unittest/run.yaml
nodeset: ubuntu-xenial-ut
- job:
name: gophercloud-acceptance-test
parent: golang-test
description: |
Run gophercloud acceptance test on master branch
run: .zuul/playbooks/gophercloud-acceptance-test/run.yaml
- job:
name: gophercloud-acceptance-test-queens
parent: gophercloud-acceptance-test
description: |
Run gophercloud acceptance test on queens branch
vars:
global_env:
OS_BRANCH: stable/queens
- job:
name: gophercloud-acceptance-test-rocky
parent: gophercloud-acceptance-test
description: |
Run gophercloud acceptance test on rocky branch
vars:
global_env:
OS_BRANCH: stable/rocky
- job:
name: gophercloud-acceptance-test-pike
parent: gophercloud-acceptance-test
description: |
Run gophercloud acceptance test on pike branch
vars:
global_env:
OS_BRANCH: stable/pike
- job:
name: gophercloud-acceptance-test-ocata
parent: gophercloud-acceptance-test
description: |
Run gophercloud acceptance test on ocata branch
vars:
global_env:
OS_BRANCH: stable/ocata
- job:
name: gophercloud-acceptance-test-newton
parent: gophercloud-acceptance-test
description: |
Run gophercloud acceptance test on newton branch
vars:
global_env:
OS_BRANCH: stable/newton
- job:
name: gophercloud-acceptance-test-mitaka
parent: gophercloud-acceptance-test
description: |
Run gophercloud acceptance test on mitaka branch
vars:
global_env:
OS_BRANCH: stable/mitaka
nodeset: ubuntu-trusty
- project:
name: gophercloud/gophercloud
check:
jobs:
- gophercloud-unittest
- gophercloud-acceptance-test
recheck-mitaka:
jobs:
- gophercloud-acceptance-test-mitaka
recheck-newton:
jobs:
- gophercloud-acceptance-test-newton
recheck-ocata:
jobs:
- gophercloud-acceptance-test-ocata
recheck-pike:
jobs:
- gophercloud-acceptance-test-pike
recheck-queens:
jobs:
- gophercloud-acceptance-test-queens
recheck-rocky:
jobs:
- gophercloud-acceptance-test-rocky
periodic:
jobs:
- gophercloud-unittest
- gophercloud-acceptance-test
# Tips
## Implementing default logging and re-authentication attempts
You can implement custom logging and/or limit re-auth attempts by creating a custom HTTP client
like the following and setting it as the provider client's HTTP Client (via the
`gophercloud.ProviderClient.HTTPClient` field):
```go
//...
// LogRoundTripper satisfies the http.RoundTripper interface and is used to
// customize the default Gophercloud RoundTripper to allow for logging.
type LogRoundTripper struct {
rt http.RoundTripper
numReauthAttempts int
}
// newHTTPClient return a custom HTTP client that allows for logging relevant
// information before and after the HTTP request.
func newHTTPClient() http.Client {
return http.Client{
Transport: &LogRoundTripper{
rt: http.DefaultTransport,
},
}
}
// RoundTrip performs a round-trip HTTP request and logs relevant information about it.
func (lrt *LogRoundTripper) RoundTrip(request *http.Request) (*http.Response, error) {
glog.Infof("Request URL: %s\n", request.URL)
response, err := lrt.rt.RoundTrip(request)
if response == nil {
return nil, err
}
if response.StatusCode == http.StatusUnauthorized {
if lrt.numReauthAttempts == 3 {
return response, fmt.Errorf("Tried to re-authenticate 3 times with no success.")
}
lrt.numReauthAttempts++
}
glog.Debugf("Response Status: %s\n", response.Status)
return response, nil
}
endpoint := "https://127.0.0.1/auth"
pc := openstack.NewClient(endpoint)
pc.HTTPClient = newHTTPClient()
//...
```
## Implementing custom objects
OpenStack request/response objects may differ among variable names or types.
### Custom request objects
To pass custom options to a request, implement the desired `<ACTION>OptsBuilder` interface. For
example, to pass in
```go
type MyCreateServerOpts struct {
Name string
Size int
}
```
to `servers.Create`, simply implement the `servers.CreateOptsBuilder` interface:
```go
func (o MyCreateServeropts) ToServerCreateMap() (map[string]interface{}, error) {
return map[string]interface{}{
"name": o.Name,
"size": o.Size,
}, nil
}
```
create an instance of your custom options object, and pass it to `servers.Create`:
```go
// ...
myOpts := MyCreateServerOpts{
Name: "s1",
Size: "100",
}
server, err := servers.Create(computeClient, myOpts).Extract()
// ...
```
### Custom response objects
Some OpenStack services have extensions. Extensions that are supported in Gophercloud can be
combined to create a custom object:
```go
// ...
type MyVolume struct {
volumes.Volume
tenantattr.VolumeExt
}
var v struct {
MyVolume `json:"volume"`
}
err := volumes.Get(client, volID).ExtractInto(&v)
// ...
```
## Overriding default `UnmarshalJSON` method
For some response objects, a field may be a custom type or may be allowed to take on
different types. In these cases, overriding the default `UnmarshalJSON` method may be
necessary. To do this, declare the JSON `struct` field tag as "-" and create an `UnmarshalJSON`
method on the type:
```go
// ...
type MyVolume struct {
ID string `json: "id"`
TimeCreated time.Time `json: "-"`
}
func (r *MyVolume) UnmarshalJSON(b []byte) error {
type tmp MyVolume
var s struct {
tmp
TimeCreated gophercloud.JSONRFC3339MilliNoZ `json:"created_at"`
}
err := json.Unmarshal(b, &s)
if err != nil {
return err
}
*r = Volume(s.tmp)
r.TimeCreated = time.Time(s.CreatedAt)
return err
}
// ...
```
# Compute
## Floating IPs
* `github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingip` is now `github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips`
* `floatingips.Associate` and `floatingips.Disassociate` have been removed.
* `floatingips.DisassociateOpts` is now required to disassociate a Floating IP.
## Security Groups
* `secgroups.AddServerToGroup` is now `secgroups.AddServer`.
* `secgroups.RemoveServerFromGroup` is now `secgroups.RemoveServer`.
## Servers
* `servers.Reboot` now requires a `servers.RebootOpts` struct:
```golang
rebootOpts := &servers.RebootOpts{
Type: servers.SoftReboot,
}
res := servers.Reboot(client, server.ID, rebootOpts)
```
# Identity
## V3
### Tokens
* `Token.ExpiresAt` is now of type `gophercloud.JSONRFC3339Milli` instead of
`time.Time`
......@@ -127,7 +127,7 @@ new resource in the `server` variable (a
## Advanced Usage
Have a look at the [FAQ](./FAQ.md) for some tips on customizing the way Gophercloud works.
Have a look at the [FAQ](./docs/FAQ.md) for some tips on customizing the way Gophercloud works.
## Backwards-Compatibility Guarantees
......@@ -140,4 +140,20 @@ See the [contributing guide](./.github/CONTRIBUTING.md).
## Help and feedback
If you're struggling with something or have spotted a potential bug, feel free
to submit an issue to our [bug tracker](/issues).
to submit an issue to our [bug tracker](https://github.com/gophercloud/gophercloud/issues).
## Thank You
We'd like to extend special thanks and appreciation to the following:
### OpenLab
<a href="http://openlabtesting.org/"><img src="./docs/assets/openlab.png" width="600px"></a>
OpenLab is providing a full CI environment to test each PR and merge for a variety of OpenStack releases.
### VEXXHOST
<a href="https://vexxhost.com/"><img src="./docs/assets/vexxhost.png" width="600px"></a>
VEXXHOST is providing their services to assist with the development and testing of Gophercloud.
## On Pull Requests
- Before you start a PR there needs to be a Github issue and a discussion about it
on that issue with a core contributor, even if it's just a 'SGTM'.
- A PR's description must reference the issue it closes with a `For <ISSUE NUMBER>` (e.g. For #293).
- A PR's description must contain link(s) to the line(s) in the OpenStack
source code (on Github) that prove(s) the PR code to be valid. Links to documentation
are not good enough. The link(s) should be to a non-`master` branch. For example,
a pull request implementing the creation of a Neutron v2 subnet might put the
following link in the description:
https://github.com/openstack/neutron/blob/stable/mitaka/neutron/api/v2/attributes.py#L749
From that link, a reviewer (or user) can verify the fields in the request/response
objects in the PR.
- A PR that is in-progress should have `[wip]` in front of the PR's title. When
ready for review, remove the `[wip]` and ping a core contributor with an `@`.
- Forcing PRs to be small can have the effect of users submitting PRs in a hierarchical chain, with
one depending on the next. If a PR depends on another one, it should have a [Pending #PRNUM]
prefix in the PR title. In addition, it will be the PR submitter's responsibility to remove the
[Pending #PRNUM] tag once the PR has been updated with the merged, dependent PR. That will
let reviewers know it is ready to review.
- A PR should be small. Even if you intend on implementing an entire
service, a PR should only be one route of that service
(e.g. create server or get server, but not both).
- Unless explicitly asked, do not squash commits in the middle of a review; only
append. It makes it difficult for the reviewer to see what's changed from one
review to the next.
## On Code
- In re design: follow as closely as is reasonable the code already in the library.
Most operations (e.g. create, delete) admit the same design.
- Unit tests and acceptance (integration) tests must be written to cover each PR.
Tests for operations with several options (e.g. list, create) should include all
the options in the tests. This will allow users to verify an operation on their
own infrastructure and see an example of usage.
- If in doubt, ask in-line on the PR.
### File Structure
- The following should be used in most cases:
- `requests.go`: contains all the functions that make HTTP requests and the
types associated with the HTTP request (parameters for URL, body, etc)
- `results.go`: contains all the response objects and their methods
- `urls.go`: contains the endpoints to which the requests are made
### Naming
- For methods on a type in `results.go`, the receiver should be named `r` and the
variable into which it will be unmarshalled `s`.
- Functions in `requests.go`, with the exception of functions that return a
`pagination.Pager`, should be named returns of the name `r`.
- Functions in `requests.go` that accept request bodies should accept as their
last parameter an `interface` named `<Action>OptsBuilder` (eg `CreateOptsBuilder`).
This `interface` should have at the least a method named `To<Resource><Action>Map`
(eg `ToPortCreateMap`).
- Functions in `requests.go` that accept query strings should accept as their
last parameter an `interface` named `<Action>OptsBuilder` (eg `ListOptsBuilder`).
This `interface` should have at the least a method named `To<Resource><Action>Query`
(eg `ToServerListQuery`).
......@@ -9,25 +9,45 @@ ProviderClient representing an active session on that provider.
Its fields are the union of those recognized by each identity implementation and
provider.
An example of manually providing authentication information:
opts := gophercloud.AuthOptions{
IdentityEndpoint: "https://openstack.example.com:5000/v2.0",
Username: "{username}",
Password: "{password}",
TenantID: "{tenant_id}",
}
provider, err := openstack.AuthenticatedClient(opts)
An example of using AuthOptionsFromEnv(), where the environment variables can
be read from a file, such as a standard openrc file:
opts, err := openstack.AuthOptionsFromEnv()
provider, err := openstack.AuthenticatedClient(opts)
*/
type AuthOptions struct {
// IdentityEndpoint specifies the HTTP endpoint that is required to work with
// the Identity API of the appropriate version. While it's ultimately needed by
// all of the identity services, it will often be populated by a provider-level
// function.
//
// The IdentityEndpoint is typically referred to as the "auth_url" or
// "OS_AUTH_URL" in the information provided by the cloud operator.
IdentityEndpoint string `json:"-"`
// Username is required if using Identity V2 API. Consult with your provider's
// control panel to discover your account's username. In Identity V3, either
// UserID or a combination of Username and DomainID or DomainName are needed.
Username string `json:"username,omitempty"`
UserID string `json:"id,omitempty"`
UserID string `json:"-"`
Password string `json:"password,omitempty"`
// At most one of DomainID and DomainName must be provided if using Username
// with Identity V3. Otherwise, either are optional.
DomainID string `json:"id,omitempty"`
DomainID string `json:"-"`
DomainName string `json:"name,omitempty"`
// The TenantID and TenantName fields are optional for the Identity V2 API.
......@@ -39,7 +59,7 @@ type AuthOptions struct {
// If DomainID or DomainName are provided, they will also apply to TenantName.
// It is not currently possible to authenticate with Username and a Domain
// and scope to a Project in a different Domain by using TenantName. To
// accomplish that, the ProjectID will need to be provided to the TenantID
// accomplish that, the ProjectID will need to be provided as the TenantID
// option.
TenantID string `json:"tenantId,omitempty"`
TenantName string `json:"tenantName,omitempty"`
......@@ -50,15 +70,34 @@ type AuthOptions struct {
// false, it will not cache these settings, but re-authentication will not be
// possible. This setting defaults to false.
//
// NOTE: The reauth function will try to re-authenticate endlessly if left unchecked.
// The way to limit the number of attempts is to provide a custom HTTP client to the provider client
// and provide a transport that implements the RoundTripper interface and stores the number of failed retries.
// For an example of this, see here: https://github.com/rackspace/rack/blob/1.0.0/auth/clients.go#L311
// NOTE: The reauth function will try to re-authenticate endlessly if left
// unchecked. The way to limit the number of attempts is to provide a custom
// HTTP client to the provider client and provide a transport that implements
// the RoundTripper interface and stores the number of failed retries. For an
// example of this, see here:
// https://github.com/rackspace/rack/blob/1.0.0/auth/clients.go#L311
AllowReauth bool `json:"-"`
// TokenID allows users to authenticate (possibly as another user) with an
// authentication token ID.
TokenID string `json:"-"`
// Scope determines the scoping of the authentication request.
Scope *AuthScope `json:"-"`
// Authentication through Application Credentials requires supplying name, project and secret
// For project we can use TenantID
ApplicationCredentialID string `json:"-"`
ApplicationCredentialName string `json:"-"`
ApplicationCredentialSecret string `json:"-"`
}
// AuthScope allows a created token to be limited to a specific domain or project.
type AuthScope struct {