-
Notifications
You must be signed in to change notification settings - Fork 40.1k
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
OpenStack Keystone Token Authentication #25391
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -24,11 +24,12 @@ import ( | |
"k8s.io/kubernetes/pkg/auth/authenticator/bearertoken" | ||
"k8s.io/kubernetes/pkg/serviceaccount" | ||
"k8s.io/kubernetes/pkg/util/crypto" | ||
"k8s.io/kubernetes/plugin/pkg/auth/authenticator/password/keystone" | ||
keystonePassword "k8s.io/kubernetes/plugin/pkg/auth/authenticator/password/keystone" | ||
"k8s.io/kubernetes/plugin/pkg/auth/authenticator/password/passwordfile" | ||
"k8s.io/kubernetes/plugin/pkg/auth/authenticator/request/basicauth" | ||
"k8s.io/kubernetes/plugin/pkg/auth/authenticator/request/union" | ||
"k8s.io/kubernetes/plugin/pkg/auth/authenticator/request/x509" | ||
keystoneToken "k8s.io/kubernetes/plugin/pkg/auth/authenticator/token/keystone" | ||
"k8s.io/kubernetes/plugin/pkg/auth/authenticator/token/oidc" | ||
"k8s.io/kubernetes/plugin/pkg/auth/authenticator/token/tokenfile" | ||
"k8s.io/kubernetes/plugin/pkg/auth/authenticator/token/webhook" | ||
|
@@ -46,9 +47,11 @@ type AuthenticatorConfig struct { | |
ServiceAccountKeyFile string | ||
ServiceAccountLookup bool | ||
ServiceAccountTokenGetter serviceaccount.ServiceAccountTokenGetter | ||
KeystoneURL string | ||
WebhookTokenAuthnConfigFile string | ||
WebhookTokenAuthnCacheTTL time.Duration | ||
KeystoneURL string | ||
KeystoneConfig string | ||
KeystoneAuthMode string | ||
} | ||
|
||
// New returns an authenticator.Request or an error that supports the standard | ||
|
@@ -96,12 +99,20 @@ func New(config AuthenticatorConfig) (authenticator.Request, error) { | |
authenticators = append(authenticators, serviceAccountAuth) | ||
} | ||
|
||
if len(config.KeystoneURL) > 0 { | ||
keystoneAuth, err := newAuthenticatorFromKeystoneURL(config.KeystoneURL) | ||
if err != nil { | ||
return nil, err | ||
if len(config.KeystoneConfig) > 0 { | ||
if config.KeystoneAuthMode == "token" { | ||
keystoneTokenAuth, err := newTokenAuthenticatorFromKeystoneConfig(config.KeystoneConfig) | ||
if err != nil { | ||
return nil, err | ||
} | ||
authenticators = append(authenticators, keystoneTokenAuth) | ||
} else if config.KeystoneAuthMode == "password" { | ||
keystonePasswordAuth, err := newPasswordAuthenticatorFromKeystoneURL(config.KeystoneURL) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is weird... you have set KeystoneConfig to a non-empty value to get here, but then it is ignored and KeystoneURL is used instead? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Your right. This is a left over from when I tried to unify the two configs but then was asked to not do that in this patchset. I'll fix it. |
||
if err != nil { | ||
return nil, err | ||
} | ||
authenticators = append(authenticators, keystonePasswordAuth) | ||
} | ||
authenticators = append(authenticators, keystoneAuth) | ||
} | ||
|
||
if len(config.WebhookTokenAuthnConfigFile) > 0 { | ||
|
@@ -190,14 +201,23 @@ func newAuthenticatorFromClientCAFile(clientCAFile string) (authenticator.Reques | |
return x509.New(opts, x509.CommonNameUserConversion), nil | ||
} | ||
|
||
// newAuthenticatorFromTokenFile returns an authenticator.Request or an error | ||
func newAuthenticatorFromKeystoneURL(keystoneURL string) (authenticator.Request, error) { | ||
keystoneAuthenticator, err := keystone.NewKeystoneAuthenticator(keystoneURL) | ||
// newPasswordAuthenticatorFromTokenFile returns an authenticator.Request or an error | ||
func newPasswordAuthenticatorFromKeystoneURL(keystoneURL string) (authenticator.Request, error) { | ||
keystonePasswordAuthenticator, err := keystonePassword.NewKeystoneAuthenticator(keystoneURL) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return basicauth.New(keystonePasswordAuthenticator), nil | ||
} | ||
|
||
// newTokenAuthenticatorFromTokenFile returns an authenticator.Request or an error | ||
func newTokenAuthenticatorFromKeystoneConfig(keystoneConfigFile string) (authenticator.Request, error) { | ||
keystoneTokenAuthenticator, err := keystoneToken.NewKeystoneAuthenticator(keystoneConfigFile) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return basicauth.New(keystoneAuthenticator), nil | ||
return bearertoken.New(keystoneTokenAuthenticator), nil | ||
} | ||
|
||
func newWebhookTokenAuthenticator(webhookConfigFile string, ttl time.Duration) (authenticator.Request, error) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
/* | ||
Copyright 2016 The Kubernetes Authors All rights reserved. | ||
|
||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
|
||
http://www.apache.org/licenses/LICENSE-2.0 | ||
|
||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*/ | ||
|
||
package openstack | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"io" | ||
"os" | ||
"strings" | ||
|
||
"github.com/golang/glog" | ||
"github.com/rackspace/gophercloud" | ||
"github.com/rackspace/gophercloud/openstack" | ||
"gopkg.in/gcfg.v1" | ||
) | ||
|
||
type KeystoneAuthOpts struct { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this package seems like a weird place for this, but I'm not sure where a better place would be. does this map to a config file or struct in keystone, or is it something made up for Kubernetes' use? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It was pulled out from existing code in Kubernetes that authenticated with OpenStack to do stuff like volume and load balancer management and made generic enough that it can be shared between the CloudProviders use case and the Keystone Authn/z plugin. The more specific code was in pkg/cloudprovider/providers/openstack/openstack.go |
||
AuthUrl string `gcfg:"auth-url"` | ||
Username string | ||
UserId string `gcfg:"user-id"` | ||
Password string | ||
ApiKey string `gcfg:"api-key"` | ||
TenantId string `gcfg:"tenant-id"` | ||
TenantName string `gcfg:"tenant-name"` | ||
DomainId string `gcfg:"domain-id"` | ||
DomainName string `gcfg:"domain-name"` | ||
Region string | ||
} | ||
|
||
type Config struct { | ||
Global KeystoneAuthOpts `gcfg:"Global"` | ||
} | ||
|
||
func (cfg KeystoneAuthOpts) ToAuthOptions() gophercloud.AuthOptions { | ||
return gophercloud.AuthOptions{ | ||
IdentityEndpoint: cfg.AuthUrl, | ||
Username: cfg.Username, | ||
UserID: cfg.UserId, | ||
Password: cfg.Password, | ||
APIKey: cfg.ApiKey, | ||
TenantID: cfg.TenantId, | ||
TenantName: cfg.TenantName, | ||
DomainID: cfg.DomainId, | ||
DomainName: cfg.DomainName, | ||
|
||
// Persistent service, so we need to be able to renew tokens. | ||
AllowReauth: true, | ||
} | ||
} | ||
|
||
func ReadConfig(config io.Reader) (Config, error) { | ||
if config == nil { | ||
err := fmt.Errorf("no OpenStack config file given") | ||
return Config{}, err | ||
} | ||
|
||
var cfg Config | ||
err := gcfg.ReadInto(&cfg, config) | ||
return cfg, err | ||
} | ||
|
||
func ConfigToProvider(config io.Reader) (Config, *gophercloud.ProviderClient, error) { | ||
cfg, err := ReadConfig(config) | ||
if err != nil { | ||
return cfg, nil, err | ||
} | ||
provider, err := openstack.AuthenticatedClient(cfg.Global.ToAuthOptions()) | ||
if err != nil { | ||
return cfg, nil, err | ||
} | ||
return cfg, provider, nil | ||
} | ||
|
||
func ConfigFileToProvider(configPath string) (Config, *gophercloud.ProviderClient, error) { | ||
var cf *os.File | ||
var err error | ||
cf, err = os.Open(configPath) | ||
if err != nil { | ||
glog.Fatalf("Couldn't open configuration %s: %#v", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. a util method shouldn't be making decisions about exiting the process |
||
configPath, err) | ||
} | ||
defer cf.Close() | ||
cfg, provider, err := ConfigToProvider(cf) | ||
if err != nil { | ||
return cfg, nil, err | ||
} | ||
if !strings.HasPrefix(cfg.Global.AuthUrl, "https") { | ||
return cfg, nil, errors.New("Auth URL should be secure and start with https") | ||
} | ||
if cfg.Global.AuthUrl == "" { | ||
return cfg, nil, errors.New("Auth URL is empty") | ||
} | ||
return cfg, provider, nil | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see why password and token auth are mutually exclusive here... I'd sort of expect to be able to set up keystone basic auth (with just the URL, as you can today?) and/or keystone token auth (by providing a config file like this?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried that originally, but it would try and authenticate to both, not just one, which always broke. If there is a way to enable them both at the same time, that would be great. Maybe I missed something?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it'll try each in turn until one succeeds... if a bearer token is passed, the token auth would get called, if basic auth is passed, the password one would get called.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah. but it wasn't doing that. At the time, I think I had both the token plugin and the basicauth plugin combined into one plugin with both types of validators in it. Maybe that was the problem?