mirror of
https://github.com/vmware-tanzu/pinniped.git
synced 2026-06-03 21:46:46 +00:00
Force the use of secure TLS config
This change updates the TLS config used by all pinniped components. There are no configuration knobs associated with this change. Thus this change tightens our static defaults. There are four TLS config levels: 1. Secure (TLS 1.3 only) 2. Default (TLS 1.2+ best ciphers that are well supported) 3. Default LDAP (TLS 1.2+ with less good ciphers) 4. Legacy (currently unused, TLS 1.2+ with all non-broken ciphers) Highlights per component: 1. pinniped CLI - uses "secure" config against KAS - uses "default" for all other connections 2. concierge - uses "secure" config as an aggregated API server - uses "default" config as a impersonation proxy API server - uses "secure" config against KAS - uses "default" config for JWT authenticater (mostly, see code) - no changes to webhook authenticater (see code) 3. supervisor - uses "default" config as a server - uses "secure" config against KAS - uses "default" config against OIDC IDPs - uses "default LDAP" config against LDAP IDPs Signed-off-by: Monis Khan <mok@vmware.com>
This commit is contained in:
@@ -9,6 +9,8 @@ import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
|
||||
"k8s.io/client-go/util/cert"
|
||||
|
||||
auth1alpha1 "go.pinniped.dev/generated/latest/apis/concierge/authentication/v1alpha1"
|
||||
)
|
||||
|
||||
@@ -23,19 +25,20 @@ type Closer interface {
|
||||
// CABundle returns a PEM-encoded CA bundle from the provided spec. If the provided spec is nil, a
|
||||
// nil CA bundle will be returned. If the provided spec contains a CA bundle that is not properly
|
||||
// encoded, an error will be returned.
|
||||
func CABundle(spec *auth1alpha1.TLSSpec) ([]byte, error) {
|
||||
func CABundle(spec *auth1alpha1.TLSSpec) (*x509.CertPool, []byte, error) {
|
||||
if spec == nil || len(spec.CertificateAuthorityData) == 0 {
|
||||
return nil, nil
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
pem, err := base64.StdEncoding.DecodeString(spec.CertificateAuthorityData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if ok := x509.NewCertPool().AppendCertsFromPEM(pem); !ok {
|
||||
return nil, fmt.Errorf("certificateAuthorityData is not valid PEM")
|
||||
rootCAs, err := cert.NewPoolFromBytes(pem)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("certificateAuthorityData is not valid PEM: %w", err)
|
||||
}
|
||||
|
||||
return pem, nil
|
||||
return rootCAs, pem, nil
|
||||
}
|
||||
|
||||
@@ -6,9 +6,13 @@
|
||||
package jwtcachefiller
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
coreosoidc "github.com/coreos/go-oidc/v3/oidc"
|
||||
"github.com/go-logr/logr"
|
||||
"gopkg.in/square/go-jose.v2"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
@@ -23,6 +27,7 @@ import (
|
||||
pinnipedauthenticator "go.pinniped.dev/internal/controller/authenticator"
|
||||
"go.pinniped.dev/internal/controller/authenticator/authncache"
|
||||
"go.pinniped.dev/internal/controllerlib"
|
||||
"go.pinniped.dev/internal/net/phttp"
|
||||
)
|
||||
|
||||
// These default values come from the way that the Supervisor issues and signs tokens. We make these
|
||||
@@ -145,7 +150,7 @@ func (c *controller) extractValueAsJWTAuthenticator(value authncache.Value) *jwt
|
||||
|
||||
// newJWTAuthenticator creates a jwt authenticator from the provided spec.
|
||||
func newJWTAuthenticator(spec *auth1alpha1.JWTAuthenticatorSpec) (*jwtAuthenticator, error) {
|
||||
caBundle, err := pinnipedauthenticator.CABundle(spec.TLS)
|
||||
rootCAs, caBundle, err := pinnipedauthenticator.CABundle(spec.TLS)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid TLS configuration: %w", err)
|
||||
}
|
||||
@@ -167,20 +172,51 @@ func newJWTAuthenticator(spec *auth1alpha1.JWTAuthenticatorSpec) (*jwtAuthentica
|
||||
groupsClaim = defaultGroupsClaim
|
||||
}
|
||||
|
||||
authenticator, err := oidc.New(oidc.Options{
|
||||
// copied from Kube OIDC code
|
||||
issuerURL, err := url.Parse(spec.Issuer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if issuerURL.Scheme != "https" {
|
||||
return nil, fmt.Errorf("issuer (%q) has invalid scheme (%q), require 'https'", spec.Issuer, issuerURL.Scheme)
|
||||
}
|
||||
|
||||
client := phttp.Default(rootCAs)
|
||||
client.Timeout = 30 * time.Second // copied from Kube OIDC code
|
||||
|
||||
ctx := coreosoidc.ClientContext(context.Background(), client)
|
||||
|
||||
provider, err := coreosoidc.NewProvider(ctx, spec.Issuer)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not initialize provider: %w", err)
|
||||
}
|
||||
providerJSON := &struct {
|
||||
JWKSURL string `json:"jwks_uri"`
|
||||
}{}
|
||||
if err := provider.Claims(providerJSON); err != nil {
|
||||
return nil, fmt.Errorf("could not get provider jwks_uri: %w", err) // should be impossible because coreosoidc.NewProvider validates this
|
||||
}
|
||||
if len(providerJSON.JWKSURL) == 0 {
|
||||
return nil, fmt.Errorf("issuer %q does not have jwks_uri set", spec.Issuer)
|
||||
}
|
||||
|
||||
oidcAuthenticator, err := oidc.New(oidc.Options{
|
||||
IssuerURL: spec.Issuer,
|
||||
KeySet: coreosoidc.NewRemoteKeySet(ctx, providerJSON.JWKSURL),
|
||||
ClientID: spec.Audience,
|
||||
UsernameClaim: usernameClaim,
|
||||
GroupsClaim: groupsClaim,
|
||||
SupportedSigningAlgs: defaultSupportedSigningAlgos(),
|
||||
CAContentProvider: caContentProvider,
|
||||
// this is still needed for distributed claim resolution, meaning this uses a http client that does not honor our TLS config
|
||||
// TODO fix when we pick up https://github.com/kubernetes/kubernetes/pull/106141
|
||||
CAContentProvider: caContentProvider,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not initialize authenticator: %w", err)
|
||||
}
|
||||
|
||||
return &jwtAuthenticator{
|
||||
tokenAuthenticatorCloser: authenticator,
|
||||
tokenAuthenticatorCloser: oidcAuthenticator,
|
||||
spec: spec,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@ import (
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -35,8 +34,10 @@ import (
|
||||
pinnipedinformers "go.pinniped.dev/generated/latest/client/concierge/informers/externalversions"
|
||||
"go.pinniped.dev/internal/controller/authenticator/authncache"
|
||||
"go.pinniped.dev/internal/controllerlib"
|
||||
"go.pinniped.dev/internal/crypto/ptls"
|
||||
"go.pinniped.dev/internal/mocks/mocktokenauthenticatorcloser"
|
||||
"go.pinniped.dev/internal/testutil/testlogger"
|
||||
"go.pinniped.dev/internal/testutil/tlsserver"
|
||||
)
|
||||
|
||||
func TestController(t *testing.T) {
|
||||
@@ -57,8 +58,10 @@ func TestController(t *testing.T) {
|
||||
goodRSASigningAlgo := jose.RS256
|
||||
|
||||
mux := http.NewServeMux()
|
||||
server := httptest.NewTLSServer(mux)
|
||||
t.Cleanup(server.Close)
|
||||
server := tlsserver.TLSTestServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
tlsserver.AssertTLS(t, r, ptls.Default)
|
||||
mux.ServeHTTP(w, r)
|
||||
}), tlsserver.RecordTLSHello)
|
||||
|
||||
mux.Handle("/.well-known/openid-configuration", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
@@ -290,11 +293,7 @@ func TestController(t *testing.T) {
|
||||
Spec: *missingTLSJWTAuthenticatorSpec,
|
||||
},
|
||||
},
|
||||
wantLogs: []string{
|
||||
`jwtcachefiller-controller "level"=0 "msg"="added new jwt authenticator" "issuer"="` + goodIssuer + `" "jwtAuthenticator"={"name":"test-name"}`,
|
||||
},
|
||||
wantCacheEntries: 1,
|
||||
runTestsOnResultingAuthenticator: false, // skip the tests because the authenticator left in the cache doesn't have the CA for our test discovery server
|
||||
wantErr: `failed to build jwt authenticator: could not initialize provider: Get "` + goodIssuer + `/.well-known/openid-configuration": x509: certificate signed by unknown authority`,
|
||||
},
|
||||
{
|
||||
name: "invalid jwt authenticator CA",
|
||||
|
||||
@@ -91,7 +91,7 @@ func newWebhookAuthenticator(
|
||||
defer func() { _ = os.Remove(temp.Name()) }()
|
||||
|
||||
cluster := &clientcmdapi.Cluster{Server: spec.Endpoint}
|
||||
cluster.CertificateAuthorityData, err = pinnipedauthenticator.CABundle(spec.TLS)
|
||||
_, cluster.CertificateAuthorityData, err = pinnipedauthenticator.CABundle(spec.TLS)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid TLS configuration: %w", err)
|
||||
}
|
||||
@@ -118,5 +118,7 @@ func newWebhookAuthenticator(
|
||||
// custom proxy stuff used by the API server.
|
||||
var customDial net.DialFunc
|
||||
|
||||
// this uses a http client that does not honor our TLS config
|
||||
// TODO fix when we pick up https://github.com/kubernetes/kubernetes/pull/106155
|
||||
return webhook.New(temp.Name(), version, implicitAuds, *webhook.DefaultRetryBackoff(), customDial)
|
||||
}
|
||||
|
||||
@@ -141,7 +141,7 @@ func TestNewWebhookAuthenticator(t *testing.T) {
|
||||
TLS: &auth1alpha1.TLSSpec{CertificateAuthorityData: base64.StdEncoding.EncodeToString([]byte("bad data"))},
|
||||
}, ioutil.TempFile, clientcmd.WriteToFile)
|
||||
require.Nil(t, res)
|
||||
require.EqualError(t, err, "invalid TLS configuration: certificateAuthorityData is not valid PEM")
|
||||
require.EqualError(t, err, "invalid TLS configuration: certificateAuthorityData is not valid PEM: data does not contain any valid RSA or ECDSA certificates")
|
||||
})
|
||||
|
||||
t.Run("valid config with no TLS spec", func(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user