Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 31 additions & 2 deletions pkg/auth/authenticator/request/x509request/x509.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import (
"net/http"

"github.com/GoogleCloudPlatform/kubernetes/pkg/auth/user"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util/errors"
kerrors "github.com/GoogleCloudPlatform/kubernetes/pkg/util/errors"
"github.com/openshift/origin/pkg/auth/authenticator"
)

// UserConversion defines an interface for extracting user info from a client certificate chain
Expand Down Expand Up @@ -60,7 +61,35 @@ func (a *Authenticator) AuthenticateRequest(req *http.Request) (user.Info, bool,
}
}
}
return nil, false, errors.NewAggregate(errlist)
return nil, false, kerrors.NewAggregate(errlist)
}

// Verifier implements request.Authenticator by verifying a client cert on the request, then delegating to the wrapped auth
type Verifier struct {
opts x509.VerifyOptions
auth authenticator.Request
}

func NewVerifier(opts x509.VerifyOptions, auth authenticator.Request) authenticator.Request {
return &Verifier{opts, auth}
}

// AuthenticateRequest verifies the presented client certificates, then delegates to the wrapped auth
func (a *Verifier) AuthenticateRequest(req *http.Request) (user.Info, bool, error) {
if req.TLS == nil {
return nil, false, nil
}

var errlist []error
for _, cert := range req.TLS.PeerCertificates {
_, err := cert.Verify(a.opts)
if err != nil {
errlist = append(errlist, err)
continue
}
return a.auth.AuthenticateRequest(req)
}
return nil, false, kerrors.NewAggregate(errlist)
}

// DefaultVerifyOptions returns VerifyOptions that use the system root certificates, current time,
Expand Down
122 changes: 122 additions & 0 deletions pkg/auth/authenticator/request/x509request/x509_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"time"

"github.com/GoogleCloudPlatform/kubernetes/pkg/auth/user"
"github.com/openshift/origin/pkg/auth/authenticator"
)

const (
Expand Down Expand Up @@ -528,6 +529,127 @@ func TestX509(t *testing.T) {
}
}

func TestX509Verifier(t *testing.T) {
testCases := map[string]struct {
Insecure bool
Certs []*x509.Certificate

Opts x509.VerifyOptions

ExpectOK bool
ExpectErr bool
}{
"non-tls": {
Insecure: true,

ExpectOK: false,
ExpectErr: false,
},

"tls, no certs": {
ExpectOK: false,
ExpectErr: false,
},

"self signed": {
Opts: getDefaultVerifyOptions(t),
Certs: getCerts(t, selfSignedCert),

ExpectErr: true,
},

"server cert disallowed": {
Opts: getDefaultVerifyOptions(t),
Certs: getCerts(t, serverCert),

ExpectErr: true,
},
"server cert allowing non-client cert usages": {
Opts: x509.VerifyOptions{Roots: getRootCertPool(t)},
Certs: getCerts(t, serverCert),

ExpectOK: true,
ExpectErr: false,
},

"valid client cert": {
Opts: getDefaultVerifyOptions(t),
Certs: getCerts(t, clientCNCert),

ExpectOK: true,
ExpectErr: false,
},

"future cert": {
Opts: x509.VerifyOptions{
CurrentTime: time.Now().Add(time.Duration(-100 * time.Hour * 24 * 365)),
Roots: getRootCertPool(t),
},
Certs: getCerts(t, clientCNCert),

ExpectOK: false,
ExpectErr: true,
},
"expired cert": {
Opts: x509.VerifyOptions{
CurrentTime: time.Now().Add(time.Duration(100 * time.Hour * 24 * 365)),
Roots: getRootCertPool(t),
},
Certs: getCerts(t, clientCNCert),

ExpectOK: false,
ExpectErr: true,
},
}

for k, testCase := range testCases {
req, _ := http.NewRequest("GET", "/", nil)
if !testCase.Insecure {
req.TLS = &tls.ConnectionState{PeerCertificates: testCase.Certs}
}

authCall := false
auth := authenticator.RequestFunc(func(req *http.Request) (user.Info, bool, error) {
authCall = true
return &user.DefaultInfo{Name: "innerauth"}, true, nil
})

a := NewVerifier(testCase.Opts, auth)

user, ok, err := a.AuthenticateRequest(req)

if testCase.ExpectErr && err == nil {
t.Errorf("%s: Expected error, got none", k)
continue
}
if !testCase.ExpectErr && err != nil {
t.Errorf("%s: Got unexpected error: %v", k, err)
continue
}

if testCase.ExpectOK != ok {
t.Errorf("%s: Expected ok=%v, got %v", k, testCase.ExpectOK, ok)
continue
}

if testCase.ExpectOK {
if !authCall {
t.Errorf("%s: Expected inner auth called, wasn't")
continue
}
if "innerauth" != user.GetName() {
t.Errorf("%s: Expected user.name=%v, got %v", k, "innerauth", user.GetName())
continue
}
} else {
if authCall {
t.Errorf("%s: Expected inner auth not to be called, was")
continue
}
}
}
}

func getDefaultVerifyOptions(t *testing.T) x509.VerifyOptions {
options := DefaultVerifyOptions()
options.Roots = getRootCertPool(t)
Expand Down
28 changes: 28 additions & 0 deletions pkg/cmd/server/crypto/crypto.go
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,34 @@ func IPAddressesDNSNames(hosts []string) ([]net.IP, []string) {
return ips, dns
}

func CertsFromPEM(pemCerts []byte) ([]*x509.Certificate, error) {
ok := false
certs := []*x509.Certificate{}
for len(pemCerts) > 0 {
var block *pem.Block
block, pemCerts = pem.Decode(pemCerts)
if block == nil {
break
}
if block.Type != "CERTIFICATE" || len(block.Headers) != 0 {
continue
}

cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return certs, err
}

certs = append(certs, cert)
ok = true
}

if !ok {
return certs, errors.New("Could not read any certificates")
}
return certs, nil
}

// Can be used as a certificate in http.Transport TLSClientConfig
func newClientCertificateTemplate(subject pkix.Name) (*x509.Certificate, error) {
return &x509.Certificate{
Expand Down
Loading