remove the top-level folders for versions

remove scripts
This commit is contained in:
Chao Xu
2016-09-28 15:08:08 -07:00
parent 6631b2769f
commit a6d206121d
2242 changed files with 0 additions and 541007 deletions

View File

@@ -0,0 +1,200 @@
/*
Copyright 2016 The Kubernetes Authors.
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 testing
import (
"bytes"
"crypto/rand"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/json"
"encoding/pem"
"fmt"
"io/ioutil"
"math/big"
"net"
"net/http"
"net/http/httptest"
"net/url"
"os"
"path"
"path/filepath"
"testing"
"time"
"github.com/coreos/go-oidc/jose"
"github.com/coreos/go-oidc/key"
"github.com/coreos/go-oidc/oidc"
)
// NewOIDCProvider provides a bare minimum OIDC IdP Server useful for testing.
func NewOIDCProvider(t *testing.T, issuerPath string) *OIDCProvider {
privKey, err := key.GeneratePrivateKey()
if err != nil {
t.Fatalf("Cannot create OIDC Provider: %v", err)
return nil
}
op := &OIDCProvider{
Mux: http.NewServeMux(),
PrivKey: privKey,
issuerPath: issuerPath,
}
op.Mux.HandleFunc(path.Join(issuerPath, "/.well-known/openid-configuration"), op.handleConfig)
op.Mux.HandleFunc(path.Join(issuerPath, "/keys"), op.handleKeys)
return op
}
type OIDCProvider struct {
Mux *http.ServeMux
PCFG oidc.ProviderConfig
PrivKey *key.PrivateKey
issuerPath string
}
func (op *OIDCProvider) ServeTLSWithKeyPair(cert, key string) (*httptest.Server, error) {
srv := httptest.NewUnstartedServer(op.Mux)
srv.TLS = &tls.Config{Certificates: make([]tls.Certificate, 1)}
var err error
srv.TLS.Certificates[0], err = tls.LoadX509KeyPair(cert, key)
if err != nil {
return nil, fmt.Errorf("Cannot load cert/key pair: %v", err)
}
srv.StartTLS()
// The issuer's URL is extended by an optional path. This ensures that the plugin can
// handle issuers that use a non-root path for discovery (see kubernetes/kubernetes#29749).
srv.URL = srv.URL + op.issuerPath
u, err := url.Parse(srv.URL)
if err != nil {
return nil, err
}
pathFor := func(p string) *url.URL {
u2 := *u // Shallow copy.
u2.Path = path.Join(u2.Path, p)
return &u2
}
op.PCFG = oidc.ProviderConfig{
Issuer: u,
AuthEndpoint: pathFor("/auth"),
TokenEndpoint: pathFor("/token"),
KeysEndpoint: pathFor("/keys"),
ResponseTypesSupported: []string{"code"},
SubjectTypesSupported: []string{"public"},
IDTokenSigningAlgValues: []string{"RS256"},
}
return srv, nil
}
func (op *OIDCProvider) handleConfig(w http.ResponseWriter, req *http.Request) {
b, err := json.Marshal(&op.PCFG)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.Write(b)
}
func (op *OIDCProvider) handleKeys(w http.ResponseWriter, req *http.Request) {
keys := struct {
Keys []jose.JWK `json:"keys"`
}{
Keys: []jose.JWK{op.PrivKey.JWK()},
}
b, err := json.Marshal(keys)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Cache-Control", fmt.Sprintf("public, max-age=%d", int(time.Hour.Seconds())))
w.Header().Set("Expires", time.Now().Add(time.Hour).Format(time.RFC1123))
w.Header().Set("Content-Type", "application/json")
w.Write(b)
}
// generateSelfSignedCert generates a self-signed cert/key pairs and writes to the certPath/keyPath.
// This method is mostly identical to crypto.GenerateSelfSignedCert except for the 'IsCA' and 'KeyUsage'
// in the certificate template. (Maybe we can merge these two methods).
func GenerateSelfSignedCert(t *testing.T, host, certPath, keyPath string) {
priv, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
t.Fatal(err)
}
template := x509.Certificate{
SerialNumber: big.NewInt(1),
Subject: pkix.Name{
CommonName: fmt.Sprintf("%s@%d", host, time.Now().Unix()),
},
NotBefore: time.Now(),
NotAfter: time.Now().Add(time.Hour * 24 * 365),
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
IsCA: true,
}
if ip := net.ParseIP(host); ip != nil {
template.IPAddresses = append(template.IPAddresses, ip)
} else {
template.DNSNames = append(template.DNSNames, host)
}
derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
if err != nil {
t.Fatal(err)
}
// Generate cert
certBuffer := bytes.Buffer{}
if err := pem.Encode(&certBuffer, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil {
t.Fatal(err)
}
// Generate key
keyBuffer := bytes.Buffer{}
if err := pem.Encode(&keyBuffer, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)}); err != nil {
t.Fatal(err)
}
// Write cert
if err := os.MkdirAll(filepath.Dir(certPath), os.FileMode(0755)); err != nil {
t.Fatal(err)
}
if err := ioutil.WriteFile(certPath, certBuffer.Bytes(), os.FileMode(0644)); err != nil {
t.Fatal(err)
}
// Write key
if err := os.MkdirAll(filepath.Dir(keyPath), os.FileMode(0755)); err != nil {
t.Fatal(err)
}
if err := ioutil.WriteFile(keyPath, keyBuffer.Bytes(), os.FileMode(0600)); err != nil {
t.Fatal(err)
}
}

View File

@@ -0,0 +1,105 @@
/*
Copyright 2016 The Kubernetes Authors.
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 gcp
import (
"net/http"
"time"
"github.com/golang/glog"
"golang.org/x/net/context"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
"k8s.io/client-go/1.5/rest"
)
func init() {
if err := rest.RegisterAuthProviderPlugin("gcp", newGCPAuthProvider); err != nil {
glog.Fatalf("Failed to register gcp auth plugin: %v", err)
}
}
type gcpAuthProvider struct {
tokenSource oauth2.TokenSource
persister rest.AuthProviderConfigPersister
}
func newGCPAuthProvider(_ string, gcpConfig map[string]string, persister rest.AuthProviderConfigPersister) (rest.AuthProvider, error) {
ts, err := newCachedTokenSource(gcpConfig["access-token"], gcpConfig["expiry"], persister)
if err != nil {
return nil, err
}
return &gcpAuthProvider{ts, persister}, nil
}
func (g *gcpAuthProvider) WrapTransport(rt http.RoundTripper) http.RoundTripper {
return &oauth2.Transport{
Source: g.tokenSource,
Base: rt,
}
}
func (g *gcpAuthProvider) Login() error { return nil }
type cachedTokenSource struct {
source oauth2.TokenSource
accessToken string
expiry time.Time
persister rest.AuthProviderConfigPersister
}
func newCachedTokenSource(accessToken, expiry string, persister rest.AuthProviderConfigPersister) (*cachedTokenSource, error) {
var expiryTime time.Time
if parsedTime, err := time.Parse(time.RFC3339Nano, expiry); err == nil {
expiryTime = parsedTime
}
ts, err := google.DefaultTokenSource(context.Background(), "https://www.googleapis.com/auth/cloud-platform")
if err != nil {
return nil, err
}
return &cachedTokenSource{
source: ts,
accessToken: accessToken,
expiry: expiryTime,
persister: persister,
}, nil
}
func (t *cachedTokenSource) Token() (*oauth2.Token, error) {
tok := &oauth2.Token{
AccessToken: t.accessToken,
TokenType: "Bearer",
Expiry: t.expiry,
}
if tok.Valid() && !tok.Expiry.IsZero() {
return tok, nil
}
tok, err := t.source.Token()
if err != nil {
return nil, err
}
if t.persister != nil {
cached := map[string]string{
"access-token": tok.AccessToken,
"expiry": tok.Expiry.Format(time.RFC3339Nano),
}
if err := t.persister.Persist(cached); err != nil {
glog.V(4).Infof("Failed to persist token: %v", err)
}
}
return tok, nil
}

View File

@@ -0,0 +1,2 @@
assignees:
- ericchiang

View File

@@ -0,0 +1,270 @@
/*
Copyright 2016 The Kubernetes Authors.
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 oidc
import (
"encoding/base64"
"errors"
"fmt"
"net/http"
"strings"
"time"
"github.com/coreos/go-oidc/jose"
"github.com/coreos/go-oidc/oauth2"
"github.com/coreos/go-oidc/oidc"
"github.com/golang/glog"
"k8s.io/client-go/1.5/pkg/util/wait"
"k8s.io/client-go/1.5/rest"
)
const (
cfgIssuerUrl = "idp-issuer-url"
cfgClientID = "client-id"
cfgClientSecret = "client-secret"
cfgCertificateAuthority = "idp-certificate-authority"
cfgCertificateAuthorityData = "idp-certificate-authority-data"
cfgExtraScopes = "extra-scopes"
cfgIDToken = "id-token"
cfgRefreshToken = "refresh-token"
)
var (
backoff = wait.Backoff{
Duration: 1 * time.Second,
Factor: 2,
Jitter: .1,
Steps: 5,
}
)
func init() {
if err := rest.RegisterAuthProviderPlugin("oidc", newOIDCAuthProvider); err != nil {
glog.Fatalf("Failed to register oidc auth plugin: %v", err)
}
}
func newOIDCAuthProvider(_ string, cfg map[string]string, persister rest.AuthProviderConfigPersister) (rest.AuthProvider, error) {
issuer := cfg[cfgIssuerUrl]
if issuer == "" {
return nil, fmt.Errorf("Must provide %s", cfgIssuerUrl)
}
clientID := cfg[cfgClientID]
if clientID == "" {
return nil, fmt.Errorf("Must provide %s", cfgClientID)
}
clientSecret := cfg[cfgClientSecret]
if clientSecret == "" {
return nil, fmt.Errorf("Must provide %s", cfgClientSecret)
}
var certAuthData []byte
var err error
if cfg[cfgCertificateAuthorityData] != "" {
certAuthData, err = base64.StdEncoding.DecodeString(cfg[cfgCertificateAuthorityData])
if err != nil {
return nil, err
}
}
clientConfig := rest.Config{
TLSClientConfig: rest.TLSClientConfig{
CAFile: cfg[cfgCertificateAuthority],
CAData: certAuthData,
},
}
trans, err := rest.TransportFor(&clientConfig)
if err != nil {
return nil, err
}
hc := &http.Client{Transport: trans}
providerCfg, err := oidc.FetchProviderConfig(hc, issuer)
if err != nil {
return nil, fmt.Errorf("error fetching provider config: %v", err)
}
scopes := strings.Split(cfg[cfgExtraScopes], ",")
oidcCfg := oidc.ClientConfig{
HTTPClient: hc,
Credentials: oidc.ClientCredentials{
ID: clientID,
Secret: clientSecret,
},
ProviderConfig: providerCfg,
Scope: append(scopes, oidc.DefaultScope...),
}
client, err := oidc.NewClient(oidcCfg)
if err != nil {
return nil, fmt.Errorf("error creating OIDC Client: %v", err)
}
oClient := &oidcClient{client}
var initialIDToken jose.JWT
if cfg[cfgIDToken] != "" {
initialIDToken, err = jose.ParseJWT(cfg[cfgIDToken])
if err != nil {
return nil, err
}
}
return &oidcAuthProvider{
initialIDToken: initialIDToken,
refresher: &idTokenRefresher{
client: oClient,
cfg: cfg,
persister: persister,
},
}, nil
}
type oidcAuthProvider struct {
refresher *idTokenRefresher
initialIDToken jose.JWT
}
func (g *oidcAuthProvider) WrapTransport(rt http.RoundTripper) http.RoundTripper {
at := &oidc.AuthenticatedTransport{
TokenRefresher: g.refresher,
RoundTripper: rt,
}
at.SetJWT(g.initialIDToken)
return &roundTripper{
wrapped: at,
refresher: g.refresher,
}
}
func (g *oidcAuthProvider) Login() error {
return errors.New("not yet implemented")
}
type OIDCClient interface {
refreshToken(rt string) (oauth2.TokenResponse, error)
verifyJWT(jwt jose.JWT) error
}
type roundTripper struct {
refresher *idTokenRefresher
wrapped *oidc.AuthenticatedTransport
}
func (r *roundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
var res *http.Response
var err error
firstTime := true
wait.ExponentialBackoff(backoff, func() (bool, error) {
if !firstTime {
var jwt jose.JWT
jwt, err = r.refresher.Refresh()
if err != nil {
return true, nil
}
r.wrapped.SetJWT(jwt)
} else {
firstTime = false
}
res, err = r.wrapped.RoundTrip(req)
if err != nil {
return true, nil
}
if res.StatusCode == http.StatusUnauthorized {
return false, nil
}
return true, nil
})
return res, err
}
type idTokenRefresher struct {
cfg map[string]string
client OIDCClient
persister rest.AuthProviderConfigPersister
intialIDToken jose.JWT
}
func (r *idTokenRefresher) Verify(jwt jose.JWT) error {
claims, err := jwt.Claims()
if err != nil {
return err
}
now := time.Now()
exp, ok, err := claims.TimeClaim("exp")
switch {
case err != nil:
return fmt.Errorf("failed to parse 'exp' claim: %v", err)
case !ok:
return errors.New("missing required 'exp' claim")
case exp.Before(now):
return fmt.Errorf("token already expired at: %v", exp)
}
return nil
}
func (r *idTokenRefresher) Refresh() (jose.JWT, error) {
rt, ok := r.cfg[cfgRefreshToken]
if !ok {
return jose.JWT{}, errors.New("No valid id-token, and cannot refresh without refresh-token")
}
tokens, err := r.client.refreshToken(rt)
if err != nil {
return jose.JWT{}, fmt.Errorf("could not refresh token: %v", err)
}
jwt, err := jose.ParseJWT(tokens.IDToken)
if err != nil {
return jose.JWT{}, err
}
if tokens.RefreshToken != "" && tokens.RefreshToken != rt {
r.cfg[cfgRefreshToken] = tokens.RefreshToken
}
r.cfg[cfgIDToken] = jwt.Encode()
err = r.persister.Persist(r.cfg)
if err != nil {
return jose.JWT{}, fmt.Errorf("could not perist new tokens: %v", err)
}
return jwt, r.client.verifyJWT(jwt)
}
type oidcClient struct {
client *oidc.Client
}
func (o *oidcClient) refreshToken(rt string) (oauth2.TokenResponse, error) {
oac, err := o.client.OAuthClient()
if err != nil {
return oauth2.TokenResponse{}, err
}
return oac.RequestToken(oauth2.GrantTypeRefreshToken, rt)
}
func (o *oidcClient) verifyJWT(jwt jose.JWT) error {
return o.client.VerifyJWT(jwt)
}

View File

@@ -0,0 +1,646 @@
/*
Copyright 2016 The Kubernetes Authors.
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 oidc
import (
"encoding/base64"
"errors"
"fmt"
"io/ioutil"
"net/http"
"os"
"path"
"reflect"
"strings"
"testing"
"time"
"github.com/coreos/go-oidc/jose"
"github.com/coreos/go-oidc/key"
"github.com/coreos/go-oidc/oauth2"
"k8s.io/client-go/1.5/pkg/util/diff"
"k8s.io/client-go/1.5/pkg/util/wait"
oidctesting "k8s.io/client-go/1.5/plugin/pkg/auth/authenticator/token/oidc/testing"
)
func TestNewOIDCAuthProvider(t *testing.T) {
tempDir, err := ioutil.TempDir(os.TempDir(), "oidc_test")
if err != nil {
t.Fatalf("Cannot make temp dir %v", err)
}
cert := path.Join(tempDir, "oidc-cert")
key := path.Join(tempDir, "oidc-key")
defer os.Remove(cert)
defer os.Remove(key)
defer os.Remove(tempDir)
oidctesting.GenerateSelfSignedCert(t, "127.0.0.1", cert, key)
op := oidctesting.NewOIDCProvider(t, "")
srv, err := op.ServeTLSWithKeyPair(cert, key)
if err != nil {
t.Fatalf("Cannot start server %v", err)
}
defer srv.Close()
certData, err := ioutil.ReadFile(cert)
if err != nil {
t.Fatalf("Could not read cert bytes %v", err)
}
jwt, err := jose.NewSignedJWT(jose.Claims(map[string]interface{}{
"test": "jwt",
}), op.PrivKey.Signer())
if err != nil {
t.Fatalf("Could not create signed JWT %v", err)
}
tests := []struct {
cfg map[string]string
wantErr bool
wantInitialIDToken jose.JWT
}{
{
// A Valid configuration
cfg: map[string]string{
cfgIssuerUrl: srv.URL,
cfgCertificateAuthority: cert,
cfgClientID: "client-id",
cfgClientSecret: "client-secret",
},
},
{
// A Valid configuration with an Initial JWT
cfg: map[string]string{
cfgIssuerUrl: srv.URL,
cfgCertificateAuthority: cert,
cfgClientID: "client-id",
cfgClientSecret: "client-secret",
cfgIDToken: jwt.Encode(),
},
wantInitialIDToken: *jwt,
},
{
// Valid config, but using cfgCertificateAuthorityData
cfg: map[string]string{
cfgIssuerUrl: srv.URL,
cfgCertificateAuthorityData: base64.StdEncoding.EncodeToString(certData),
cfgClientID: "client-id",
cfgClientSecret: "client-secret",
},
},
{
// Missing client id
cfg: map[string]string{
cfgIssuerUrl: srv.URL,
cfgCertificateAuthority: cert,
cfgClientSecret: "client-secret",
},
wantErr: true,
},
{
// Missing client secret
cfg: map[string]string{
cfgIssuerUrl: srv.URL,
cfgCertificateAuthority: cert,
cfgClientID: "client-id",
},
wantErr: true,
},
{
// Missing issuer url.
cfg: map[string]string{
cfgCertificateAuthority: cert,
cfgClientID: "client-id",
cfgClientSecret: "secret",
},
wantErr: true,
},
{
// No TLS config
cfg: map[string]string{
cfgIssuerUrl: srv.URL,
cfgClientID: "client-id",
cfgClientSecret: "secret",
},
wantErr: true,
},
}
for i, tt := range tests {
ap, err := newOIDCAuthProvider("cluster.example.com", tt.cfg, nil)
if tt.wantErr {
if err == nil {
t.Errorf("case %d: want non-nil err", i)
}
continue
}
if err != nil {
t.Errorf("case %d: unexpected error on newOIDCAuthProvider: %v", i, err)
continue
}
oidcAP, ok := ap.(*oidcAuthProvider)
if !ok {
t.Errorf("case %d: expected ap to be an oidcAuthProvider", i)
continue
}
if diff := compareJWTs(tt.wantInitialIDToken, oidcAP.initialIDToken); diff != "" {
t.Errorf("case %d: compareJWTs(tt.wantInitialIDToken, oidcAP.initialIDToken)=%v", i, diff)
}
}
}
func TestWrapTranport(t *testing.T) {
oldBackoff := backoff
defer func() {
backoff = oldBackoff
}()
backoff = wait.Backoff{
Duration: 1 * time.Nanosecond,
Steps: 3,
}
privKey, err := key.GeneratePrivateKey()
if err != nil {
t.Fatalf("can't generate private key: %v", err)
}
makeToken := func(s string, exp time.Time, count int) *jose.JWT {
jwt, err := jose.NewSignedJWT(jose.Claims(map[string]interface{}{
"test": s,
"exp": exp.UTC().Unix(),
"count": count,
}), privKey.Signer())
if err != nil {
t.Fatalf("Could not create signed JWT %v", err)
}
return jwt
}
goodToken := makeToken("good", time.Now().Add(time.Hour), 0)
goodToken2 := makeToken("good", time.Now().Add(time.Hour), 1)
expiredToken := makeToken("good", time.Now().Add(-time.Hour), 0)
str := func(s string) *string {
return &s
}
tests := []struct {
cfgIDToken *jose.JWT
cfgRefreshToken *string
expectRequests []testRoundTrip
expectRefreshes []testRefresh
expectPersists []testPersist
wantStatus int
wantErr bool
}{
{
// Initial JWT is set, it is good, it is set as bearer.
cfgIDToken: goodToken,
expectRequests: []testRoundTrip{
{
expectBearerToken: goodToken.Encode(),
returnHTTPStatus: 200,
},
},
wantStatus: 200,
},
{
// Initial JWT is set, but it's expired, so it gets refreshed.
cfgIDToken: expiredToken,
cfgRefreshToken: str("rt1"),
expectRefreshes: []testRefresh{
{
expectRefreshToken: "rt1",
returnTokens: oauth2.TokenResponse{
IDToken: goodToken.Encode(),
},
},
},
expectRequests: []testRoundTrip{
{
expectBearerToken: goodToken.Encode(),
returnHTTPStatus: 200,
},
},
expectPersists: []testPersist{
{
cfg: map[string]string{
cfgIDToken: goodToken.Encode(),
cfgRefreshToken: "rt1",
},
},
},
wantStatus: 200,
},
{
// Initial JWT is set, but it's expired, so it gets refreshed - this
// time the refresh token itself is also refreshed
cfgIDToken: expiredToken,
cfgRefreshToken: str("rt1"),
expectRefreshes: []testRefresh{
{
expectRefreshToken: "rt1",
returnTokens: oauth2.TokenResponse{
IDToken: goodToken.Encode(),
RefreshToken: "rt2",
},
},
},
expectRequests: []testRoundTrip{
{
expectBearerToken: goodToken.Encode(),
returnHTTPStatus: 200,
},
},
expectPersists: []testPersist{
{
cfg: map[string]string{
cfgIDToken: goodToken.Encode(),
cfgRefreshToken: "rt2",
},
},
},
wantStatus: 200,
},
{
// Initial JWT is not set, so it gets refreshed.
cfgRefreshToken: str("rt1"),
expectRefreshes: []testRefresh{
{
expectRefreshToken: "rt1",
returnTokens: oauth2.TokenResponse{
IDToken: goodToken.Encode(),
},
},
},
expectRequests: []testRoundTrip{
{
expectBearerToken: goodToken.Encode(),
returnHTTPStatus: 200,
},
},
expectPersists: []testPersist{
{
cfg: map[string]string{
cfgIDToken: goodToken.Encode(),
cfgRefreshToken: "rt1",
},
},
},
wantStatus: 200,
},
{
// Expired token, but no refresh token.
cfgIDToken: expiredToken,
wantErr: true,
},
{
// Initial JWT is not set, so it gets refreshed, but the server
// rejects it when it is used, so it refreshes again, which
// succeeds.
cfgRefreshToken: str("rt1"),
expectRefreshes: []testRefresh{
{
expectRefreshToken: "rt1",
returnTokens: oauth2.TokenResponse{
IDToken: goodToken.Encode(),
},
},
{
expectRefreshToken: "rt1",
returnTokens: oauth2.TokenResponse{
IDToken: goodToken2.Encode(),
},
},
},
expectRequests: []testRoundTrip{
{
expectBearerToken: goodToken.Encode(),
returnHTTPStatus: http.StatusUnauthorized,
},
{
expectBearerToken: goodToken2.Encode(),
returnHTTPStatus: http.StatusOK,
},
},
expectPersists: []testPersist{
{
cfg: map[string]string{
cfgIDToken: goodToken.Encode(),
cfgRefreshToken: "rt1",
},
},
{
cfg: map[string]string{
cfgIDToken: goodToken2.Encode(),
cfgRefreshToken: "rt1",
},
},
},
wantStatus: 200,
},
{
// Initial JWT is but the server rejects it when it is used, so it
// refreshes again, which succeeds.
cfgRefreshToken: str("rt1"),
cfgIDToken: goodToken,
expectRefreshes: []testRefresh{
{
expectRefreshToken: "rt1",
returnTokens: oauth2.TokenResponse{
IDToken: goodToken2.Encode(),
},
},
},
expectRequests: []testRoundTrip{
{
expectBearerToken: goodToken.Encode(),
returnHTTPStatus: http.StatusUnauthorized,
},
{
expectBearerToken: goodToken2.Encode(),
returnHTTPStatus: http.StatusOK,
},
},
expectPersists: []testPersist{
{
cfg: map[string]string{
cfgIDToken: goodToken2.Encode(),
cfgRefreshToken: "rt1",
},
},
},
wantStatus: 200,
},
}
for i, tt := range tests {
client := &testOIDCClient{
refreshes: tt.expectRefreshes,
}
persister := &testPersister{
tt.expectPersists,
}
cfg := map[string]string{}
if tt.cfgIDToken != nil {
cfg[cfgIDToken] = tt.cfgIDToken.Encode()
}
if tt.cfgRefreshToken != nil {
cfg[cfgRefreshToken] = *tt.cfgRefreshToken
}
ap := &oidcAuthProvider{
refresher: &idTokenRefresher{
client: client,
cfg: cfg,
persister: persister,
},
}
if tt.cfgIDToken != nil {
ap.initialIDToken = *tt.cfgIDToken
}
tstRT := &testRoundTripper{
tt.expectRequests,
}
rt := ap.WrapTransport(tstRT)
req, err := http.NewRequest("GET", "http://cluster.example.com", nil)
if err != nil {
t.Errorf("case %d: unexpected error making request: %v", i, err)
}
res, err := rt.RoundTrip(req)
if tt.wantErr {
if err == nil {
t.Errorf("case %d: Expected non-nil error", i)
}
} else if err != nil {
t.Errorf("case %d: unexpected error making round trip: %v", i, err)
} else {
if res.StatusCode != tt.wantStatus {
t.Errorf("case %d: want=%d, got=%d", i, tt.wantStatus, res.StatusCode)
}
}
if err = client.verify(); err != nil {
t.Errorf("case %d: %v", i, err)
}
if err = persister.verify(); err != nil {
t.Errorf("case %d: %v", i, err)
}
if err = tstRT.verify(); err != nil {
t.Errorf("case %d: %v", i, err)
continue
}
}
}
type testRoundTrip struct {
expectBearerToken string
returnHTTPStatus int
}
type testRoundTripper struct {
trips []testRoundTrip
}
func (t *testRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
if len(t.trips) == 0 {
return nil, errors.New("unexpected RoundTrip call")
}
var trip testRoundTrip
trip, t.trips = t.trips[0], t.trips[1:]
var bt string
var parts []string
auth := strings.TrimSpace(req.Header.Get("Authorization"))
if auth == "" {
goto Compare
}
parts = strings.Split(auth, " ")
if len(parts) < 2 || strings.ToLower(parts[0]) != "bearer" {
goto Compare
}
bt = parts[1]
Compare:
if trip.expectBearerToken != bt {
return nil, fmt.Errorf("want bearerToken=%v, got=%v", trip.expectBearerToken, bt)
}
return &http.Response{
StatusCode: trip.returnHTTPStatus,
}, nil
}
func (t *testRoundTripper) verify() error {
if l := len(t.trips); l > 0 {
return fmt.Errorf("%d uncalled round trips", l)
}
return nil
}
type testPersist struct {
cfg map[string]string
returnErr error
}
type testPersister struct {
persists []testPersist
}
func (t *testPersister) Persist(cfg map[string]string) error {
if len(t.persists) == 0 {
return errors.New("unexpected persist call")
}
var persist testPersist
persist, t.persists = t.persists[0], t.persists[1:]
if !reflect.DeepEqual(persist.cfg, cfg) {
return fmt.Errorf("Unexpected cfg: %v", diff.ObjectDiff(persist.cfg, cfg))
}
return persist.returnErr
}
func (t *testPersister) verify() error {
if l := len(t.persists); l > 0 {
return fmt.Errorf("%d uncalled persists", l)
}
return nil
}
type testRefresh struct {
expectRefreshToken string
returnErr error
returnTokens oauth2.TokenResponse
}
type testOIDCClient struct {
refreshes []testRefresh
}
func (o *testOIDCClient) refreshToken(rt string) (oauth2.TokenResponse, error) {
if len(o.refreshes) == 0 {
return oauth2.TokenResponse{}, errors.New("unexpected refresh request")
}
var refresh testRefresh
refresh, o.refreshes = o.refreshes[0], o.refreshes[1:]
if rt != refresh.expectRefreshToken {
return oauth2.TokenResponse{}, fmt.Errorf("want rt=%v, got=%v",
refresh.expectRefreshToken,
rt)
}
if refresh.returnErr != nil {
return oauth2.TokenResponse{}, refresh.returnErr
}
return refresh.returnTokens, nil
}
func (o *testOIDCClient) verifyJWT(jwt jose.JWT) error {
claims, err := jwt.Claims()
if err != nil {
return err
}
claim, _, _ := claims.StringClaim("test")
if claim != "good" {
return errors.New("bad token")
}
return nil
}
func (t *testOIDCClient) verify() error {
if l := len(t.refreshes); l > 0 {
return fmt.Errorf("%d uncalled refreshes", l)
}
return nil
}
func compareJWTs(a, b jose.JWT) string {
if a.Encode() == b.Encode() {
return ""
}
var aClaims, bClaims jose.Claims
for _, j := range []struct {
claims *jose.Claims
jwt jose.JWT
}{
{&aClaims, a},
{&bClaims, b},
} {
var err error
*j.claims, err = j.jwt.Claims()
if err != nil {
*j.claims = jose.Claims(map[string]interface{}{
"msg": "bad claims",
"err": err,
})
}
}
return diff.ObjectDiff(aClaims, bClaims)
}

View File

@@ -0,0 +1,23 @@
/*
Copyright 2016 The Kubernetes Authors.
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 auth
import (
// Initialize all known client auth plugins.
_ "k8s.io/client-go/1.5/plugin/pkg/client/auth/gcp"
_ "k8s.io/client-go/1.5/plugin/pkg/client/auth/oidc"
)