mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-12 13:31:52 +00:00
Merge pull request #25270 from bobbyrullo/deps
Implement OIDC client AuthProvider
This commit is contained in:
commit
7170c8910d
10
Godeps/Godeps.json
generated
10
Godeps/Godeps.json
generated
@ -485,23 +485,23 @@
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/coreos/go-oidc/http",
|
||||
"Rev": "d7cb66526fffc811d602b6770581064f4b66b507"
|
||||
"Rev": "5cf2aa52da8c574d3aa4458f471ad6ae2240fe6b"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/coreos/go-oidc/jose",
|
||||
"Rev": "d7cb66526fffc811d602b6770581064f4b66b507"
|
||||
"Rev": "5cf2aa52da8c574d3aa4458f471ad6ae2240fe6b"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/coreos/go-oidc/key",
|
||||
"Rev": "d7cb66526fffc811d602b6770581064f4b66b507"
|
||||
"Rev": "5cf2aa52da8c574d3aa4458f471ad6ae2240fe6b"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/coreos/go-oidc/oauth2",
|
||||
"Rev": "d7cb66526fffc811d602b6770581064f4b66b507"
|
||||
"Rev": "5cf2aa52da8c574d3aa4458f471ad6ae2240fe6b"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/coreos/go-oidc/oidc",
|
||||
"Rev": "d7cb66526fffc811d602b6770581064f4b66b507"
|
||||
"Rev": "5cf2aa52da8c574d3aa4458f471ad6ae2240fe6b"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/coreos/go-semver/semver",
|
||||
|
@ -17,99 +17,24 @@ limitations under the License.
|
||||
package oidc
|
||||
|
||||
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"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/coreos/go-oidc/jose"
|
||||
"github.com/coreos/go-oidc/key"
|
||||
"github.com/coreos/go-oidc/oidc"
|
||||
|
||||
"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 {
|
||||
u, err := url.Parse(s)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to parse url: %v", err)
|
||||
}
|
||||
return u
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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()
|
||||
func generateToken(t *testing.T, op *oidctesting.OIDCProvider, 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.Add(usernameClaim, value)
|
||||
if groups != nil && groupsClaim != "" {
|
||||
@ -124,79 +49,16 @@ func (op *oidcProvider) generateToken(t *testing.T, iss, sub, aud string, userna
|
||||
return jwt.Encode()
|
||||
}
|
||||
|
||||
func (op *oidcProvider) generateGoodToken(t *testing.T, 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))
|
||||
func generateGoodToken(t *testing.T, op *oidctesting.OIDCProvider, iss, sub, aud string, usernameClaim, value, groupsClaim string, groups []string) string {
|
||||
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 {
|
||||
return op.generateToken(t, iss, sub, aud, usernameClaim, value, groupsClaim, groups, time.Now(), time.Now().Add(time.Hour)) + "randombits"
|
||||
func generateMalformedToken(t *testing.T, op *oidctesting.OIDCProvider, iss, sub, aud string, usernameClaim, value, groupsClaim string, groups []string) string {
|
||||
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 {
|
||||
return op.generateToken(t, 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 generateExpiredToken(t *testing.T, op *oidctesting.OIDCProvider, iss, sub, aud string, usernameClaim, value, groupsClaim string, groups []string) string {
|
||||
return generateToken(t, op, iss, sub, aud, usernameClaim, value, groupsClaim, groups, time.Now().Add(-2*time.Hour), time.Now().Add(-1*time.Hour))
|
||||
}
|
||||
|
||||
func TestOIDCDiscoveryTimeout(t *testing.T) {
|
||||
@ -217,20 +79,17 @@ func TestOIDCDiscoveryNoKeyEndpoint(t *testing.T) {
|
||||
defer os.Remove(cert)
|
||||
defer os.Remove(key)
|
||||
|
||||
generateSelfSignedCert(t, "127.0.0.1", cert, key)
|
||||
oidctesting.GenerateSelfSignedCert(t, "127.0.0.1", cert, key)
|
||||
|
||||
op := newOIDCProvider(t)
|
||||
srv := httptest.NewUnstartedServer(op.mux)
|
||||
srv.TLS = &tls.Config{Certificates: make([]tls.Certificate, 1)}
|
||||
srv.TLS.Certificates[0], err = tls.LoadX509KeyPair(cert, key)
|
||||
op := oidctesting.NewOIDCProvider(t)
|
||||
srv, err := op.ServeTLSWithKeyPair(cert, key)
|
||||
if err != nil {
|
||||
t.Fatalf("Cannot load cert/key pair: %v", err)
|
||||
t.Fatalf("Cannot start server %v", err)
|
||||
}
|
||||
srv.StartTLS()
|
||||
defer srv.Close()
|
||||
|
||||
op.pcfg = oidc.ProviderConfig{
|
||||
Issuer: mustParseURL(t, srv.URL), // An invalid ProviderConfig. Keys endpoint is required.
|
||||
op.PCFG = oidc.ProviderConfig{
|
||||
Issuer: oidctesting.MustParseURL(srv.URL), // An invalid ProviderConfig. Keys endpoint is required.
|
||||
}
|
||||
|
||||
_, err = New(OIDCOptions{srv.URL, "client-foo", cert, "sub", "", 0, 0})
|
||||
@ -241,13 +100,13 @@ func TestOIDCDiscoveryNoKeyEndpoint(t *testing.T) {
|
||||
|
||||
func TestOIDCDiscoverySecureConnection(t *testing.T) {
|
||||
// Verify that plain HTTP issuer URL is forbidden.
|
||||
op := newOIDCProvider(t)
|
||||
srv := httptest.NewServer(op.mux)
|
||||
op := oidctesting.NewOIDCProvider(t)
|
||||
srv := httptest.NewServer(op.Mux)
|
||||
defer srv.Close()
|
||||
|
||||
op.pcfg = oidc.ProviderConfig{
|
||||
Issuer: mustParseURL(t, srv.URL),
|
||||
KeysEndpoint: mustParseURL(t, srv.URL+"/keys"),
|
||||
op.PCFG = oidc.ProviderConfig{
|
||||
Issuer: oidctesting.MustParseURL(srv.URL),
|
||||
KeysEndpoint: oidctesting.MustParseURL(srv.URL + "/keys"),
|
||||
}
|
||||
|
||||
expectErr := fmt.Errorf("'oidc-issuer-url' (%q) has invalid scheme (%q), require 'https'", srv.URL, "http")
|
||||
@ -268,22 +127,19 @@ func TestOIDCDiscoverySecureConnection(t *testing.T) {
|
||||
defer os.Remove(cert2)
|
||||
defer os.Remove(key2)
|
||||
|
||||
generateSelfSignedCert(t, "127.0.0.1", cert1, key1)
|
||||
generateSelfSignedCert(t, "127.0.0.1", cert2, key2)
|
||||
oidctesting.GenerateSelfSignedCert(t, "127.0.0.1", cert1, key1)
|
||||
oidctesting.GenerateSelfSignedCert(t, "127.0.0.1", cert2, key2)
|
||||
|
||||
// Create a TLS server using cert/key pair 1.
|
||||
tlsSrv := httptest.NewUnstartedServer(op.mux)
|
||||
tlsSrv.TLS = &tls.Config{Certificates: make([]tls.Certificate, 1)}
|
||||
tlsSrv.TLS.Certificates[0], err = tls.LoadX509KeyPair(cert1, key1)
|
||||
tlsSrv, err := op.ServeTLSWithKeyPair(cert1, key1)
|
||||
if err != nil {
|
||||
t.Fatalf("Cannot load cert/key pair: %v", err)
|
||||
t.Fatalf("Cannot start server: %v", err)
|
||||
}
|
||||
tlsSrv.StartTLS()
|
||||
defer tlsSrv.Close()
|
||||
|
||||
op.pcfg = oidc.ProviderConfig{
|
||||
Issuer: mustParseURL(t, tlsSrv.URL),
|
||||
KeysEndpoint: mustParseURL(t, tlsSrv.URL+"/keys"),
|
||||
op.PCFG = oidc.ProviderConfig{
|
||||
Issuer: oidctesting.MustParseURL(tlsSrv.URL),
|
||||
KeysEndpoint: oidctesting.MustParseURL(tlsSrv.URL + "/keys"),
|
||||
}
|
||||
|
||||
// Create a client using cert2, should fail.
|
||||
@ -303,29 +159,18 @@ func TestOIDCAuthentication(t *testing.T) {
|
||||
defer os.Remove(cert)
|
||||
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.
|
||||
op := newOIDCProvider(t)
|
||||
srv := httptest.NewUnstartedServer(op.mux)
|
||||
srv.TLS = &tls.Config{Certificates: make([]tls.Certificate, 1)}
|
||||
srv.TLS.Certificates[0], err = tls.LoadX509KeyPair(cert, key)
|
||||
op := oidctesting.NewOIDCProvider(t)
|
||||
srv, err := op.ServeTLSWithKeyPair(cert, key)
|
||||
if err != nil {
|
||||
t.Fatalf("Cannot load cert/key pair: %v", err)
|
||||
t.Fatalf("Cannot start server: %v", err)
|
||||
}
|
||||
srv.StartTLS()
|
||||
defer srv.Close()
|
||||
|
||||
// A provider config with all required fields.
|
||||
op.pcfg = oidc.ProviderConfig{
|
||||
Issuer: mustParseURL(t, srv.URL),
|
||||
AuthEndpoint: mustParseURL(t, srv.URL+"/auth"),
|
||||
TokenEndpoint: mustParseURL(t, srv.URL+"/token"),
|
||||
KeysEndpoint: mustParseURL(t, srv.URL+"/keys"),
|
||||
ResponseTypesSupported: []string{"code"},
|
||||
SubjectTypesSupported: []string{"public"},
|
||||
IDTokenSigningAlgValues: []string{"RS256"},
|
||||
}
|
||||
op.AddMinimalProviderConfig(srv)
|
||||
|
||||
tests := []struct {
|
||||
userClaim string
|
||||
@ -338,7 +183,7 @@ func TestOIDCAuthentication(t *testing.T) {
|
||||
{
|
||||
"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")},
|
||||
true,
|
||||
"",
|
||||
@ -347,7 +192,7 @@ func TestOIDCAuthentication(t *testing.T) {
|
||||
// Use user defined claim (email here).
|
||||
"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"},
|
||||
true,
|
||||
"",
|
||||
@ -356,7 +201,7 @@ func TestOIDCAuthentication(t *testing.T) {
|
||||
// Use user defined claim (email here).
|
||||
"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"},
|
||||
true,
|
||||
"",
|
||||
@ -365,7 +210,7 @@ func TestOIDCAuthentication(t *testing.T) {
|
||||
// Use user defined claim (email here).
|
||||
"email",
|
||||
"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"}},
|
||||
true,
|
||||
"",
|
||||
@ -373,7 +218,7 @@ func TestOIDCAuthentication(t *testing.T) {
|
||||
{
|
||||
"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,
|
||||
false,
|
||||
"oidc: unable to verify JWT signature: no matching keys",
|
||||
@ -382,7 +227,7 @@ func TestOIDCAuthentication(t *testing.T) {
|
||||
// Invalid 'aud'.
|
||||
"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,
|
||||
false,
|
||||
"oidc: JWT claims invalid: invalid claims, 'aud' claim and 'client_id' do not match",
|
||||
@ -391,7 +236,7 @@ func TestOIDCAuthentication(t *testing.T) {
|
||||
// Invalid issuer.
|
||||
"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,
|
||||
false,
|
||||
"oidc: JWT claims invalid: invalid claim value: 'iss'.",
|
||||
@ -399,7 +244,7 @@ func TestOIDCAuthentication(t *testing.T) {
|
||||
{
|
||||
"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,
|
||||
false,
|
||||
"oidc: JWT claims invalid: token is expired",
|
||||
|
194
plugin/pkg/auth/authenticator/token/oidc/testing/provider.go
Normal file
194
plugin/pkg/auth/authenticator/token/oidc/testing/provider.go
Normal file
@ -0,0 +1,194 @@
|
||||
/*
|
||||
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"
|
||||
"net/url"
|
||||
"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) AddMinimalProviderConfig(srv *httptest.Server) {
|
||||
op.PCFG = oidc.ProviderConfig{
|
||||
Issuer: MustParseURL(srv.URL),
|
||||
AuthEndpoint: MustParseURL(srv.URL + "/auth"),
|
||||
TokenEndpoint: MustParseURL(srv.URL + "/token"),
|
||||
KeysEndpoint: MustParseURL(srv.URL + "/keys"),
|
||||
ResponseTypesSupported: []string{"code"},
|
||||
SubjectTypesSupported: []string{"public"},
|
||||
IDTokenSigningAlgValues: []string{"RS256"},
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
func MustParseURL(s string) *url.URL {
|
||||
u, err := url.Parse(s)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("Failed to parse url: %v", err))
|
||||
}
|
||||
return u
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
2
plugin/pkg/client/auth/oidc/OWNERS
Normal file
2
plugin/pkg/client/auth/oidc/OWNERS
Normal file
@ -0,0 +1,2 @@
|
||||
assignees:
|
||||
- bobbyrullo
|
270
plugin/pkg/client/auth/oidc/oidc.go
Normal file
270
plugin/pkg/client/auth/oidc/oidc.go
Normal file
@ -0,0 +1,270 @@
|
||||
/*
|
||||
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 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/kubernetes/pkg/client/restclient"
|
||||
"k8s.io/kubernetes/pkg/util/wait"
|
||||
)
|
||||
|
||||
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 := restclient.RegisterAuthProviderPlugin("oidc", newOIDCAuthProvider); err != nil {
|
||||
glog.Fatalf("Failed to register oidc auth plugin: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func newOIDCAuthProvider(_ string, cfg map[string]string, persister restclient.AuthProviderConfigPersister) (restclient.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 := restclient.Config{
|
||||
TLSClientConfig: restclient.TLSClientConfig{
|
||||
CAFile: cfg[cfgCertificateAuthority],
|
||||
CAData: certAuthData,
|
||||
},
|
||||
}
|
||||
|
||||
trans, err := restclient.TransportFor(&clientConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hc := &http.Client{Transport: trans}
|
||||
|
||||
providerCfg, err := oidc.FetchProviderConfig(hc, strings.TrimSuffix(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 restclient.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)
|
||||
}
|
642
plugin/pkg/client/auth/oidc/oidc_test.go
Normal file
642
plugin/pkg/client/auth/oidc/oidc_test.go
Normal file
@ -0,0 +1,642 @@
|
||||
/*
|
||||
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 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/kubernetes/pkg/util/diff"
|
||||
"k8s.io/kubernetes/pkg/util/wait"
|
||||
oidctesting "k8s.io/kubernetes/plugin/pkg/auth/authenticator/token/oidc/testing"
|
||||
)
|
||||
|
||||
func TestNewOIDCAuthProvider(t *testing.T) {
|
||||
cert := path.Join(os.TempDir(), "oidc-cert")
|
||||
key := path.Join(os.TempDir(), "oidc-key")
|
||||
|
||||
defer os.Remove(cert)
|
||||
defer os.Remove(key)
|
||||
|
||||
oidctesting.GenerateSelfSignedCert(t, "127.0.0.1", cert, key)
|
||||
op := oidctesting.NewOIDCProvider(t)
|
||||
srv, err := op.ServeTLSWithKeyPair(cert, key)
|
||||
op.AddMinimalProviderConfig(srv)
|
||||
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)
|
||||
}
|
@ -19,4 +19,5 @@ package plugins
|
||||
import (
|
||||
// Initialize all known client auth plugins.
|
||||
_ "k8s.io/kubernetes/plugin/pkg/client/auth/gcp"
|
||||
_ "k8s.io/kubernetes/plugin/pkg/client/auth/oidc"
|
||||
)
|
||||
|
3
vendor/github.com/coreos/go-oidc/jose/sig.go
generated
vendored
3
vendor/github.com/coreos/go-oidc/jose/sig.go
generated
vendored
@ -2,7 +2,6 @@ package jose
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Verifier interface {
|
||||
@ -17,7 +16,7 @@ type Signer interface {
|
||||
}
|
||||
|
||||
func NewVerifier(jwk JWK) (Verifier, error) {
|
||||
if strings.ToUpper(jwk.Type) != "RSA" {
|
||||
if jwk.Type != "RSA" {
|
||||
return nil, fmt.Errorf("unsupported key type %q", jwk.Type)
|
||||
}
|
||||
|
||||
|
3
vendor/github.com/coreos/go-oidc/jose/sig_hmac.go
generated
vendored
3
vendor/github.com/coreos/go-oidc/jose/sig_hmac.go
generated
vendored
@ -7,7 +7,6 @@ import (
|
||||
_ "crypto/sha256"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type VerifierHMAC struct {
|
||||
@ -21,7 +20,7 @@ type SignerHMAC struct {
|
||||
}
|
||||
|
||||
func NewVerifierHMAC(jwk JWK) (*VerifierHMAC, error) {
|
||||
if strings.ToUpper(jwk.Alg) != "HS256" {
|
||||
if jwk.Alg != "" && jwk.Alg != "HS256" {
|
||||
return nil, fmt.Errorf("unsupported key algorithm %q", jwk.Alg)
|
||||
}
|
||||
|
||||
|
3
vendor/github.com/coreos/go-oidc/jose/sig_rsa.go
generated
vendored
3
vendor/github.com/coreos/go-oidc/jose/sig_rsa.go
generated
vendored
@ -5,7 +5,6 @@ import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type VerifierRSA struct {
|
||||
@ -20,7 +19,7 @@ type SignerRSA struct {
|
||||
}
|
||||
|
||||
func NewVerifierRSA(jwk JWK) (*VerifierRSA, error) {
|
||||
if strings.ToUpper(jwk.Alg) != "RS256" {
|
||||
if jwk.Alg != "" && jwk.Alg != "RS256" {
|
||||
return nil, fmt.Errorf("unsupported key algorithm %q", jwk.Alg)
|
||||
}
|
||||
|
||||
|
2
vendor/github.com/coreos/go-oidc/key/key.go
generated
vendored
2
vendor/github.com/coreos/go-oidc/key/key.go
generated
vendored
@ -20,7 +20,7 @@ type PublicKey struct {
|
||||
}
|
||||
|
||||
func (k *PublicKey) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(k.jwk)
|
||||
return json.Marshal(&k.jwk)
|
||||
}
|
||||
|
||||
func (k *PublicKey) UnmarshalJSON(data []byte) error {
|
||||
|
30
vendor/github.com/coreos/go-oidc/oauth2/oauth2.go
generated
vendored
30
vendor/github.com/coreos/go-oidc/oauth2/oauth2.go
generated
vendored
@ -56,6 +56,7 @@ const (
|
||||
const (
|
||||
GrantTypeAuthCode = "authorization_code"
|
||||
GrantTypeClientCreds = "client_credentials"
|
||||
GrantTypeUserCreds = "password"
|
||||
GrantTypeImplicit = "implicit"
|
||||
GrantTypeRefreshToken = "refresh_token"
|
||||
|
||||
@ -140,6 +141,11 @@ func NewClient(hc phttp.Client, cfg Config) (c *Client, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// Return the embedded HTTP client
|
||||
func (c *Client) HttpClient() phttp.Client {
|
||||
return c.hc
|
||||
}
|
||||
|
||||
// Generate the url for initial redirect to oauth provider.
|
||||
func (c *Client) AuthCodeURL(state, accessType, prompt string) string {
|
||||
v := c.commonURLValues()
|
||||
@ -220,6 +226,30 @@ func (c *Client) ClientCredsToken(scope []string) (result TokenResponse, err err
|
||||
return parseTokenResponse(resp)
|
||||
}
|
||||
|
||||
// UserCredsToken posts the username and password to obtain a token scoped to the OAuth2 client via the "password" grant_type
|
||||
// May not be supported by all OAuth2 servers.
|
||||
func (c *Client) UserCredsToken(username, password string) (result TokenResponse, err error) {
|
||||
v := url.Values{
|
||||
"scope": {strings.Join(c.scope, " ")},
|
||||
"grant_type": {GrantTypeUserCreds},
|
||||
"username": {username},
|
||||
"password": {password},
|
||||
}
|
||||
|
||||
req, err := c.newAuthenticatedRequest(c.tokenURL.String(), v)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := c.hc.Do(req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
return parseTokenResponse(resp)
|
||||
}
|
||||
|
||||
// RequestToken requests a token from the Token Endpoint with the specified grantType.
|
||||
// If 'grantType' == GrantTypeAuthCode, then 'value' should be the authorization code.
|
||||
// If 'grantType' == GrantTypeRefreshToken, then 'value' should be the refresh token.
|
||||
|
12
vendor/github.com/coreos/go-oidc/oidc/key.go
generated
vendored
12
vendor/github.com/coreos/go-oidc/oidc/key.go
generated
vendored
@ -11,6 +11,11 @@ import (
|
||||
"github.com/coreos/go-oidc/key"
|
||||
)
|
||||
|
||||
// DefaultPublicKeySetTTL is the default TTL set on the PublicKeySet if no
|
||||
// Cache-Control header is provided by the JWK Set document endpoint.
|
||||
const DefaultPublicKeySetTTL = 24 * time.Hour
|
||||
|
||||
// NewRemotePublicKeyRepo is responsible for fetching the JWK Set document.
|
||||
func NewRemotePublicKeyRepo(hc phttp.Client, ep string) *remotePublicKeyRepo {
|
||||
return &remotePublicKeyRepo{hc: hc, ep: ep}
|
||||
}
|
||||
@ -20,6 +25,11 @@ type remotePublicKeyRepo struct {
|
||||
ep string
|
||||
}
|
||||
|
||||
// Get returns a PublicKeySet fetched from the JWK Set document endpoint. A TTL
|
||||
// is set on the Key Set to avoid it having to be re-retrieved for every
|
||||
// encryption event. This TTL is typically controlled by the endpoint returning
|
||||
// a Cache-Control header, but defaults to 24 hours if no Cache-Control header
|
||||
// is found.
|
||||
func (r *remotePublicKeyRepo) Get() (key.KeySet, error) {
|
||||
req, err := http.NewRequest("GET", r.ep, nil)
|
||||
if err != nil {
|
||||
@ -48,7 +58,7 @@ func (r *remotePublicKeyRepo) Get() (key.KeySet, error) {
|
||||
return nil, err
|
||||
}
|
||||
if !ok {
|
||||
return nil, errors.New("HTTP cache headers not set")
|
||||
ttl = DefaultPublicKeySetTTL
|
||||
}
|
||||
|
||||
exp := time.Now().UTC().Add(ttl)
|
||||
|
7
vendor/github.com/coreos/go-oidc/oidc/provider.go
generated
vendored
7
vendor/github.com/coreos/go-oidc/oidc/provider.go
generated
vendored
@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@ -618,7 +619,11 @@ func NewHTTPProviderConfigGetter(hc phttp.Client, issuerURL string) *httpProvide
|
||||
}
|
||||
|
||||
func (r *httpProviderConfigGetter) Get() (cfg ProviderConfig, err error) {
|
||||
req, err := http.NewRequest("GET", r.issuerURL+discoveryConfigPath, nil)
|
||||
// If the Issuer value contains a path component, any terminating / MUST be removed before
|
||||
// appending /.well-known/openid-configuration.
|
||||
// https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationRequest
|
||||
discoveryURL := strings.TrimSuffix(r.issuerURL, "/") + discoveryConfigPath
|
||||
req, err := http.NewRequest("GET", discoveryURL, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
9
vendor/github.com/coreos/go-oidc/oidc/transport.go
generated
vendored
9
vendor/github.com/coreos/go-oidc/oidc/transport.go
generated
vendored
@ -67,6 +67,15 @@ func (t *AuthenticatedTransport) verifiedJWT() (jose.JWT, error) {
|
||||
return t.jwt, nil
|
||||
}
|
||||
|
||||
// SetJWT sets the JWT held by the Transport.
|
||||
// This is useful for cases in which you want to set an initial JWT.
|
||||
func (t *AuthenticatedTransport) SetJWT(jwt jose.JWT) {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
|
||||
t.jwt = jwt
|
||||
}
|
||||
|
||||
func (t *AuthenticatedTransport) RoundTrip(r *http.Request) (*http.Response, error) {
|
||||
jwt, err := t.verifiedJWT()
|
||||
if err != nil {
|
||||
|
Loading…
Reference in New Issue
Block a user