1/* 2Copyright 2017 The Kubernetes Authors. 3 4Licensed under the Apache License, Version 2.0 (the "License"); 5you may not use this file except in compliance with the License. 6You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10Unless required by applicable law or agreed to in writing, software 11distributed under the License is distributed on an "AS IS" BASIS, 12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13See the License for the specific language governing permissions and 14limitations under the License. 15*/ 16 17package openstack 18 19import ( 20 "fmt" 21 "net/http" 22 "sync" 23 "time" 24 25 "github.com/gophercloud/gophercloud" 26 "github.com/gophercloud/gophercloud/openstack" 27 "k8s.io/klog" 28 29 "k8s.io/apimachinery/pkg/util/net" 30 restclient "k8s.io/client-go/rest" 31) 32 33func init() { 34 if err := restclient.RegisterAuthProviderPlugin("openstack", newOpenstackAuthProvider); err != nil { 35 klog.Fatalf("Failed to register openstack auth plugin: %s", err) 36 } 37} 38 39// DefaultTTLDuration is the time before a token gets expired. 40const DefaultTTLDuration = 10 * time.Minute 41 42// openstackAuthProvider is an authprovider for openstack. this provider reads 43// the environment variables to determine the client identity, and generates a 44// token which will be inserted into the request header later. 45type openstackAuthProvider struct { 46 ttl time.Duration 47 tokenGetter TokenGetter 48} 49 50// TokenGetter returns a bearer token that can be inserted into request. 51type TokenGetter interface { 52 Token() (string, error) 53} 54 55type tokenGetter struct { 56 authOpt *gophercloud.AuthOptions 57} 58 59// Token creates a token by authenticate with keystone. 60func (t *tokenGetter) Token() (string, error) { 61 var options gophercloud.AuthOptions 62 var err error 63 if t.authOpt == nil { 64 // reads the config from the environment 65 klog.V(4).Info("reading openstack config from the environment variables") 66 options, err = openstack.AuthOptionsFromEnv() 67 if err != nil { 68 return "", fmt.Errorf("failed to read openstack env vars: %s", err) 69 } 70 } else { 71 options = *t.authOpt 72 } 73 client, err := openstack.AuthenticatedClient(options) 74 if err != nil { 75 return "", fmt.Errorf("authentication failed: %s", err) 76 } 77 return client.TokenID, nil 78} 79 80// cachedGetter caches a token until it gets expired, after the expiration, it will 81// generate another token and cache it. 82type cachedGetter struct { 83 mutex sync.Mutex 84 tokenGetter TokenGetter 85 86 token string 87 born time.Time 88 ttl time.Duration 89} 90 91// Token returns the current available token, create a new one if expired. 92func (c *cachedGetter) Token() (string, error) { 93 c.mutex.Lock() 94 defer c.mutex.Unlock() 95 96 var err error 97 // no token or exceeds the TTL 98 if c.token == "" || time.Since(c.born) > c.ttl { 99 c.token, err = c.tokenGetter.Token() 100 if err != nil { 101 return "", fmt.Errorf("failed to get token: %s", err) 102 } 103 c.born = time.Now() 104 } 105 return c.token, nil 106} 107 108// tokenRoundTripper implements the RoundTripper interface: adding the bearer token 109// into the request header. 110type tokenRoundTripper struct { 111 http.RoundTripper 112 113 tokenGetter TokenGetter 114} 115 116var _ net.RoundTripperWrapper = &tokenRoundTripper{} 117 118// RoundTrip adds the bearer token into the request. 119func (t *tokenRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { 120 // if the authorization header already present, use it. 121 if req.Header.Get("Authorization") != "" { 122 return t.RoundTripper.RoundTrip(req) 123 } 124 125 token, err := t.tokenGetter.Token() 126 if err == nil { 127 req.Header.Set("Authorization", "Bearer "+token) 128 } else { 129 klog.V(4).Infof("failed to get token: %s", err) 130 } 131 132 return t.RoundTripper.RoundTrip(req) 133} 134 135func (t *tokenRoundTripper) WrappedRoundTripper() http.RoundTripper { return t.RoundTripper } 136 137// newOpenstackAuthProvider creates an auth provider which works with openstack 138// environment. 139func newOpenstackAuthProvider(_ string, config map[string]string, persister restclient.AuthProviderConfigPersister) (restclient.AuthProvider, error) { 140 var ttlDuration time.Duration 141 var err error 142 143 klog.Warningf("WARNING: in-tree openstack auth plugin is now deprecated. please use the \"client-keystone-auth\" kubectl/client-go credential plugin instead") 144 ttl, found := config["ttl"] 145 if !found { 146 ttlDuration = DefaultTTLDuration 147 // persist to config 148 config["ttl"] = ttlDuration.String() 149 if err = persister.Persist(config); err != nil { 150 return nil, fmt.Errorf("failed to persist config: %s", err) 151 } 152 } else { 153 ttlDuration, err = time.ParseDuration(ttl) 154 if err != nil { 155 return nil, fmt.Errorf("failed to parse ttl config: %s", err) 156 } 157 } 158 159 authOpt := gophercloud.AuthOptions{ 160 IdentityEndpoint: config["identityEndpoint"], 161 Username: config["username"], 162 Password: config["password"], 163 DomainName: config["name"], 164 TenantID: config["tenantId"], 165 TenantName: config["tenantName"], 166 } 167 168 getter := tokenGetter{} 169 // not empty 170 if (authOpt != gophercloud.AuthOptions{}) { 171 if len(authOpt.IdentityEndpoint) == 0 { 172 return nil, fmt.Errorf("empty %q in the config for openstack auth provider", "identityEndpoint") 173 } 174 getter.authOpt = &authOpt 175 } 176 177 return &openstackAuthProvider{ 178 ttl: ttlDuration, 179 tokenGetter: &getter, 180 }, nil 181} 182 183func (oap *openstackAuthProvider) WrapTransport(rt http.RoundTripper) http.RoundTripper { 184 return &tokenRoundTripper{ 185 RoundTripper: rt, 186 tokenGetter: &cachedGetter{ 187 tokenGetter: oap.tokenGetter, 188 ttl: oap.ttl, 189 }, 190 } 191} 192 193func (oap *openstackAuthProvider) Login() error { return nil } 194