mirror of
				https://github.com/k3s-io/kubernetes.git
				synced 2025-11-03 23:40:03 +00:00 
			
		
		
		
	Refactor test oidc provider into its own package
This makes it easier to test other OIDC code.
This commit is contained in:
		@@ -17,60 +17,23 @@ limitations under the License.
 | 
				
			|||||||
package oidc
 | 
					package oidc
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"bytes"
 | 
					 | 
				
			||||||
	"crypto/rand"
 | 
					 | 
				
			||||||
	"crypto/rsa"
 | 
					 | 
				
			||||||
	"crypto/tls"
 | 
					 | 
				
			||||||
	"crypto/x509"
 | 
					 | 
				
			||||||
	"crypto/x509/pkix"
 | 
					 | 
				
			||||||
	"encoding/json"
 | 
					 | 
				
			||||||
	"encoding/pem"
 | 
					 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"io/ioutil"
 | 
					 | 
				
			||||||
	"math/big"
 | 
					 | 
				
			||||||
	"net"
 | 
					 | 
				
			||||||
	"net/http"
 | 
					 | 
				
			||||||
	"net/http/httptest"
 | 
						"net/http/httptest"
 | 
				
			||||||
	"net/url"
 | 
						"net/url"
 | 
				
			||||||
	"os"
 | 
						"os"
 | 
				
			||||||
	"path"
 | 
						"path"
 | 
				
			||||||
	"path/filepath"
 | 
					 | 
				
			||||||
	"reflect"
 | 
						"reflect"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
	"testing"
 | 
						"testing"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/coreos/go-oidc/jose"
 | 
						"github.com/coreos/go-oidc/jose"
 | 
				
			||||||
	"github.com/coreos/go-oidc/key"
 | 
					 | 
				
			||||||
	"github.com/coreos/go-oidc/oidc"
 | 
						"github.com/coreos/go-oidc/oidc"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"k8s.io/kubernetes/pkg/auth/user"
 | 
						"k8s.io/kubernetes/pkg/auth/user"
 | 
				
			||||||
 | 
						oidctesting "k8s.io/kubernetes/plugin/pkg/auth/authenticator/token/oidc/testing"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type oidcProvider struct {
 | 
					 | 
				
			||||||
	mux     *http.ServeMux
 | 
					 | 
				
			||||||
	pcfg    oidc.ProviderConfig
 | 
					 | 
				
			||||||
	privKey *key.PrivateKey
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func newOIDCProvider(t *testing.T) *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,
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	op.mux.HandleFunc("/.well-known/openid-configuration", op.handleConfig)
 | 
					 | 
				
			||||||
	op.mux.HandleFunc("/keys", op.handleKeys)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return op
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func mustParseURL(t *testing.T, s string) *url.URL {
 | 
					func mustParseURL(t *testing.T, s string) *url.URL {
 | 
				
			||||||
	u, err := url.Parse(s)
 | 
						u, err := url.Parse(s)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
@@ -79,37 +42,8 @@ func mustParseURL(t *testing.T, s string) *url.URL {
 | 
				
			|||||||
	return u
 | 
						return u
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (op *oidcProvider) handleConfig(w http.ResponseWriter, req *http.Request) {
 | 
					func generateToken(t *testing.T, op *oidctesting.OIDCProvider, iss, sub, aud string, usernameClaim, value, groupsClaim string, groups []string, iat, exp time.Time) string {
 | 
				
			||||||
	b, err := json.Marshal(&op.pcfg)
 | 
						signer := op.PrivKey.Signer()
 | 
				
			||||||
	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)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (op *oidcProvider) generateToken(t *testing.T, iss, sub, aud string, usernameClaim, value, groupsClaim string, groups []string, iat, exp time.Time) string {
 | 
					 | 
				
			||||||
	signer := op.privKey.Signer()
 | 
					 | 
				
			||||||
	claims := oidc.NewClaims(iss, sub, aud, iat, exp)
 | 
						claims := oidc.NewClaims(iss, sub, aud, iat, exp)
 | 
				
			||||||
	claims.Add(usernameClaim, value)
 | 
						claims.Add(usernameClaim, value)
 | 
				
			||||||
	if groups != nil && groupsClaim != "" {
 | 
						if groups != nil && groupsClaim != "" {
 | 
				
			||||||
@@ -124,79 +58,16 @@ func (op *oidcProvider) generateToken(t *testing.T, iss, sub, aud string, userna
 | 
				
			|||||||
	return jwt.Encode()
 | 
						return jwt.Encode()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (op *oidcProvider) generateGoodToken(t *testing.T, iss, sub, aud string, usernameClaim, value, groupsClaim string, groups []string) string {
 | 
					func generateGoodToken(t *testing.T, op *oidctesting.OIDCProvider, iss, sub, aud string, usernameClaim, value, groupsClaim string, groups []string) string {
 | 
				
			||||||
	return op.generateToken(t, iss, sub, aud, usernameClaim, value, groupsClaim, groups, time.Now(), time.Now().Add(time.Hour))
 | 
						return generateToken(t, op, iss, sub, aud, usernameClaim, value, groupsClaim, groups, time.Now(), time.Now().Add(time.Hour))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (op *oidcProvider) generateMalformedToken(t *testing.T, iss, sub, aud string, usernameClaim, value, groupsClaim string, groups []string) string {
 | 
					func generateMalformedToken(t *testing.T, op *oidctesting.OIDCProvider, iss, sub, aud string, usernameClaim, value, groupsClaim string, groups []string) string {
 | 
				
			||||||
	return op.generateToken(t, iss, sub, aud, usernameClaim, value, groupsClaim, groups, time.Now(), time.Now().Add(time.Hour)) + "randombits"
 | 
						return generateToken(t, op, iss, sub, aud, usernameClaim, value, groupsClaim, groups, time.Now(), time.Now().Add(time.Hour)) + "randombits"
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (op *oidcProvider) generateExpiredToken(t *testing.T, iss, sub, aud string, usernameClaim, value, groupsClaim string, groups []string) string {
 | 
					func generateExpiredToken(t *testing.T, op *oidctesting.OIDCProvider, iss, sub, aud string, usernameClaim, value, groupsClaim string, groups []string) string {
 | 
				
			||||||
	return op.generateToken(t, iss, sub, aud, usernameClaim, value, groupsClaim, groups, time.Now().Add(-2*time.Hour), time.Now().Add(-1*time.Hour))
 | 
						return generateToken(t, op, iss, sub, aud, usernameClaim, value, groupsClaim, groups, time.Now().Add(-2*time.Hour), time.Now().Add(-1*time.Hour))
 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// 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)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestOIDCDiscoveryTimeout(t *testing.T) {
 | 
					func TestOIDCDiscoveryTimeout(t *testing.T) {
 | 
				
			||||||
@@ -217,19 +88,16 @@ func TestOIDCDiscoveryNoKeyEndpoint(t *testing.T) {
 | 
				
			|||||||
	defer os.Remove(cert)
 | 
						defer os.Remove(cert)
 | 
				
			||||||
	defer os.Remove(key)
 | 
						defer os.Remove(key)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	generateSelfSignedCert(t, "127.0.0.1", cert, key)
 | 
						oidctesting.GenerateSelfSignedCert(t, "127.0.0.1", cert, key)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	op := newOIDCProvider(t)
 | 
						op := oidctesting.NewOIDCProvider(t)
 | 
				
			||||||
	srv := httptest.NewUnstartedServer(op.mux)
 | 
						srv, err := op.ServeTLSWithKeyPair(cert, key)
 | 
				
			||||||
	srv.TLS = &tls.Config{Certificates: make([]tls.Certificate, 1)}
 | 
					 | 
				
			||||||
	srv.TLS.Certificates[0], err = tls.LoadX509KeyPair(cert, key)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		t.Fatalf("Cannot load cert/key pair: %v", err)
 | 
							t.Fatalf("Cannot start server %v", err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	srv.StartTLS()
 | 
					 | 
				
			||||||
	defer srv.Close()
 | 
						defer srv.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	op.pcfg = oidc.ProviderConfig{
 | 
						op.PCFG = oidc.ProviderConfig{
 | 
				
			||||||
		Issuer: mustParseURL(t, srv.URL), // An invalid ProviderConfig. Keys endpoint is required.
 | 
							Issuer: mustParseURL(t, srv.URL), // An invalid ProviderConfig. Keys endpoint is required.
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -241,11 +109,11 @@ func TestOIDCDiscoveryNoKeyEndpoint(t *testing.T) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
func TestOIDCDiscoverySecureConnection(t *testing.T) {
 | 
					func TestOIDCDiscoverySecureConnection(t *testing.T) {
 | 
				
			||||||
	// Verify that plain HTTP issuer URL is forbidden.
 | 
						// Verify that plain HTTP issuer URL is forbidden.
 | 
				
			||||||
	op := newOIDCProvider(t)
 | 
						op := oidctesting.NewOIDCProvider(t)
 | 
				
			||||||
	srv := httptest.NewServer(op.mux)
 | 
						srv := httptest.NewServer(op.Mux)
 | 
				
			||||||
	defer srv.Close()
 | 
						defer srv.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	op.pcfg = oidc.ProviderConfig{
 | 
						op.PCFG = oidc.ProviderConfig{
 | 
				
			||||||
		Issuer:       mustParseURL(t, srv.URL),
 | 
							Issuer:       mustParseURL(t, srv.URL),
 | 
				
			||||||
		KeysEndpoint: mustParseURL(t, srv.URL+"/keys"),
 | 
							KeysEndpoint: mustParseURL(t, srv.URL+"/keys"),
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -268,20 +136,17 @@ func TestOIDCDiscoverySecureConnection(t *testing.T) {
 | 
				
			|||||||
	defer os.Remove(cert2)
 | 
						defer os.Remove(cert2)
 | 
				
			||||||
	defer os.Remove(key2)
 | 
						defer os.Remove(key2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	generateSelfSignedCert(t, "127.0.0.1", cert1, key1)
 | 
						oidctesting.GenerateSelfSignedCert(t, "127.0.0.1", cert1, key1)
 | 
				
			||||||
	generateSelfSignedCert(t, "127.0.0.1", cert2, key2)
 | 
						oidctesting.GenerateSelfSignedCert(t, "127.0.0.1", cert2, key2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Create a TLS server using cert/key pair 1.
 | 
						// Create a TLS server using cert/key pair 1.
 | 
				
			||||||
	tlsSrv := httptest.NewUnstartedServer(op.mux)
 | 
						tlsSrv, err := op.ServeTLSWithKeyPair(cert1, key1)
 | 
				
			||||||
	tlsSrv.TLS = &tls.Config{Certificates: make([]tls.Certificate, 1)}
 | 
					 | 
				
			||||||
	tlsSrv.TLS.Certificates[0], err = tls.LoadX509KeyPair(cert1, key1)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		t.Fatalf("Cannot load cert/key pair: %v", err)
 | 
							t.Fatalf("Cannot start server: %v", err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	tlsSrv.StartTLS()
 | 
					 | 
				
			||||||
	defer tlsSrv.Close()
 | 
						defer tlsSrv.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	op.pcfg = oidc.ProviderConfig{
 | 
						op.PCFG = oidc.ProviderConfig{
 | 
				
			||||||
		Issuer:       mustParseURL(t, tlsSrv.URL),
 | 
							Issuer:       mustParseURL(t, tlsSrv.URL),
 | 
				
			||||||
		KeysEndpoint: mustParseURL(t, tlsSrv.URL+"/keys"),
 | 
							KeysEndpoint: mustParseURL(t, tlsSrv.URL+"/keys"),
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -303,21 +168,18 @@ func TestOIDCAuthentication(t *testing.T) {
 | 
				
			|||||||
	defer os.Remove(cert)
 | 
						defer os.Remove(cert)
 | 
				
			||||||
	defer os.Remove(key)
 | 
						defer os.Remove(key)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	generateSelfSignedCert(t, "127.0.0.1", cert, key)
 | 
						oidctesting.GenerateSelfSignedCert(t, "127.0.0.1", cert, key)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Create a TLS server and a client.
 | 
						// Create a TLS server and a client.
 | 
				
			||||||
	op := newOIDCProvider(t)
 | 
						op := oidctesting.NewOIDCProvider(t)
 | 
				
			||||||
	srv := httptest.NewUnstartedServer(op.mux)
 | 
						srv, err := op.ServeTLSWithKeyPair(cert, key)
 | 
				
			||||||
	srv.TLS = &tls.Config{Certificates: make([]tls.Certificate, 1)}
 | 
					 | 
				
			||||||
	srv.TLS.Certificates[0], err = tls.LoadX509KeyPair(cert, key)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		t.Fatalf("Cannot load cert/key pair: %v", err)
 | 
							t.Fatalf("Cannot start server: %v", err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	srv.StartTLS()
 | 
					 | 
				
			||||||
	defer srv.Close()
 | 
						defer srv.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// A provider config with all required fields.
 | 
						// A provider config with all required fields.
 | 
				
			||||||
	op.pcfg = oidc.ProviderConfig{
 | 
						op.PCFG = oidc.ProviderConfig{
 | 
				
			||||||
		Issuer:                  mustParseURL(t, srv.URL),
 | 
							Issuer:                  mustParseURL(t, srv.URL),
 | 
				
			||||||
		AuthEndpoint:            mustParseURL(t, srv.URL+"/auth"),
 | 
							AuthEndpoint:            mustParseURL(t, srv.URL+"/auth"),
 | 
				
			||||||
		TokenEndpoint:           mustParseURL(t, srv.URL+"/token"),
 | 
							TokenEndpoint:           mustParseURL(t, srv.URL+"/token"),
 | 
				
			||||||
@@ -338,7 +200,7 @@ func TestOIDCAuthentication(t *testing.T) {
 | 
				
			|||||||
		{
 | 
							{
 | 
				
			||||||
			"sub",
 | 
								"sub",
 | 
				
			||||||
			"",
 | 
								"",
 | 
				
			||||||
			op.generateGoodToken(t, srv.URL, "client-foo", "client-foo", "sub", "user-foo", "", nil),
 | 
								generateGoodToken(t, op, srv.URL, "client-foo", "client-foo", "sub", "user-foo", "", nil),
 | 
				
			||||||
			&user.DefaultInfo{Name: fmt.Sprintf("%s#%s", srv.URL, "user-foo")},
 | 
								&user.DefaultInfo{Name: fmt.Sprintf("%s#%s", srv.URL, "user-foo")},
 | 
				
			||||||
			true,
 | 
								true,
 | 
				
			||||||
			"",
 | 
								"",
 | 
				
			||||||
@@ -347,7 +209,7 @@ func TestOIDCAuthentication(t *testing.T) {
 | 
				
			|||||||
			// Use user defined claim (email here).
 | 
								// Use user defined claim (email here).
 | 
				
			||||||
			"email",
 | 
								"email",
 | 
				
			||||||
			"",
 | 
								"",
 | 
				
			||||||
			op.generateGoodToken(t, srv.URL, "client-foo", "client-foo", "email", "foo@example.com", "", nil),
 | 
								generateGoodToken(t, op, srv.URL, "client-foo", "client-foo", "email", "foo@example.com", "", nil),
 | 
				
			||||||
			&user.DefaultInfo{Name: "foo@example.com"},
 | 
								&user.DefaultInfo{Name: "foo@example.com"},
 | 
				
			||||||
			true,
 | 
								true,
 | 
				
			||||||
			"",
 | 
								"",
 | 
				
			||||||
@@ -356,7 +218,7 @@ func TestOIDCAuthentication(t *testing.T) {
 | 
				
			|||||||
			// Use user defined claim (email here).
 | 
								// Use user defined claim (email here).
 | 
				
			||||||
			"email",
 | 
								"email",
 | 
				
			||||||
			"",
 | 
								"",
 | 
				
			||||||
			op.generateGoodToken(t, srv.URL, "client-foo", "client-foo", "email", "foo@example.com", "groups", []string{"group1", "group2"}),
 | 
								generateGoodToken(t, op, srv.URL, "client-foo", "client-foo", "email", "foo@example.com", "groups", []string{"group1", "group2"}),
 | 
				
			||||||
			&user.DefaultInfo{Name: "foo@example.com"},
 | 
								&user.DefaultInfo{Name: "foo@example.com"},
 | 
				
			||||||
			true,
 | 
								true,
 | 
				
			||||||
			"",
 | 
								"",
 | 
				
			||||||
@@ -365,7 +227,7 @@ func TestOIDCAuthentication(t *testing.T) {
 | 
				
			|||||||
			// Use user defined claim (email here).
 | 
								// Use user defined claim (email here).
 | 
				
			||||||
			"email",
 | 
								"email",
 | 
				
			||||||
			"groups",
 | 
								"groups",
 | 
				
			||||||
			op.generateGoodToken(t, srv.URL, "client-foo", "client-foo", "email", "foo@example.com", "groups", []string{"group1", "group2"}),
 | 
								generateGoodToken(t, op, srv.URL, "client-foo", "client-foo", "email", "foo@example.com", "groups", []string{"group1", "group2"}),
 | 
				
			||||||
			&user.DefaultInfo{Name: "foo@example.com", Groups: []string{"group1", "group2"}},
 | 
								&user.DefaultInfo{Name: "foo@example.com", Groups: []string{"group1", "group2"}},
 | 
				
			||||||
			true,
 | 
								true,
 | 
				
			||||||
			"",
 | 
								"",
 | 
				
			||||||
@@ -373,7 +235,7 @@ func TestOIDCAuthentication(t *testing.T) {
 | 
				
			|||||||
		{
 | 
							{
 | 
				
			||||||
			"sub",
 | 
								"sub",
 | 
				
			||||||
			"",
 | 
								"",
 | 
				
			||||||
			op.generateMalformedToken(t, srv.URL, "client-foo", "client-foo", "sub", "user-foo", "", nil),
 | 
								generateMalformedToken(t, op, srv.URL, "client-foo", "client-foo", "sub", "user-foo", "", nil),
 | 
				
			||||||
			nil,
 | 
								nil,
 | 
				
			||||||
			false,
 | 
								false,
 | 
				
			||||||
			"oidc: unable to verify JWT signature: no matching keys",
 | 
								"oidc: unable to verify JWT signature: no matching keys",
 | 
				
			||||||
@@ -382,7 +244,7 @@ func TestOIDCAuthentication(t *testing.T) {
 | 
				
			|||||||
			// Invalid 'aud'.
 | 
								// Invalid 'aud'.
 | 
				
			||||||
			"sub",
 | 
								"sub",
 | 
				
			||||||
			"",
 | 
								"",
 | 
				
			||||||
			op.generateGoodToken(t, srv.URL, "client-foo", "client-bar", "sub", "user-foo", "", nil),
 | 
								generateGoodToken(t, op, srv.URL, "client-foo", "client-bar", "sub", "user-foo", "", nil),
 | 
				
			||||||
			nil,
 | 
								nil,
 | 
				
			||||||
			false,
 | 
								false,
 | 
				
			||||||
			"oidc: JWT claims invalid: invalid claims, 'aud' claim and 'client_id' do not match",
 | 
								"oidc: JWT claims invalid: invalid claims, 'aud' claim and 'client_id' do not match",
 | 
				
			||||||
@@ -391,7 +253,7 @@ func TestOIDCAuthentication(t *testing.T) {
 | 
				
			|||||||
			// Invalid issuer.
 | 
								// Invalid issuer.
 | 
				
			||||||
			"sub",
 | 
								"sub",
 | 
				
			||||||
			"",
 | 
								"",
 | 
				
			||||||
			op.generateGoodToken(t, "http://foo-bar.com", "client-foo", "client-foo", "sub", "user-foo", "", nil),
 | 
								generateGoodToken(t, op, "http://foo-bar.com", "client-foo", "client-foo", "sub", "user-foo", "", nil),
 | 
				
			||||||
			nil,
 | 
								nil,
 | 
				
			||||||
			false,
 | 
								false,
 | 
				
			||||||
			"oidc: JWT claims invalid: invalid claim value: 'iss'.",
 | 
								"oidc: JWT claims invalid: invalid claim value: 'iss'.",
 | 
				
			||||||
@@ -399,7 +261,7 @@ func TestOIDCAuthentication(t *testing.T) {
 | 
				
			|||||||
		{
 | 
							{
 | 
				
			||||||
			"sub",
 | 
								"sub",
 | 
				
			||||||
			"",
 | 
								"",
 | 
				
			||||||
			op.generateExpiredToken(t, srv.URL, "client-foo", "client-foo", "sub", "user-foo", "", nil),
 | 
								generateExpiredToken(t, op, srv.URL, "client-foo", "client-foo", "sub", "user-foo", "", nil),
 | 
				
			||||||
			nil,
 | 
								nil,
 | 
				
			||||||
			false,
 | 
								false,
 | 
				
			||||||
			"oidc: JWT claims invalid: token is expired",
 | 
								"oidc: JWT claims invalid: token is expired",
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										172
									
								
								plugin/pkg/auth/authenticator/token/oidc/testing/provider.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										172
									
								
								plugin/pkg/auth/authenticator/token/oidc/testing/provider.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,172 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					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 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"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
						"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) *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,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						op.Mux.HandleFunc("/.well-known/openid-configuration", op.handleConfig)
 | 
				
			||||||
 | 
						op.Mux.HandleFunc("/keys", op.handleKeys)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return op
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type OIDCProvider struct {
 | 
				
			||||||
 | 
						Mux     *http.ServeMux
 | 
				
			||||||
 | 
						PCFG    oidc.ProviderConfig
 | 
				
			||||||
 | 
						PrivKey *key.PrivateKey
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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()
 | 
				
			||||||
 | 
						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)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										75
									
								
								plugin/pkg/client/auth/oidc/oidc_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								plugin/pkg/client/auth/oidc/oidc_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,75 @@
 | 
				
			|||||||
 | 
					package oidc
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"testing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"k8s.io/kubernetes/pkg/util/diff"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/coreos/go-oidc/jose"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestNewOIDCAuthProvider(t *testing.T) {
 | 
				
			||||||
 | 
						tests := []struct {
 | 
				
			||||||
 | 
							cfg map[string]string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							wantErr            bool
 | 
				
			||||||
 | 
							wantInitialIDToken jose.JWT
 | 
				
			||||||
 | 
						}{
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								cfg: map[string]string{
 | 
				
			||||||
 | 
									cfgIssuerUrl: "auth.example.com",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						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 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(a, b)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user