Skip to content

Commit

Permalink
kestest: move server to gateway tests
Browse files Browse the repository at this point in the history
This commit refactors the KES server tests.
Since the KES stateful and stateless server
get separate APIs, this commit refactors the
existing server tests and re-implements them
as gateway tests.

A gateway is truly stateless w.r.t. identities
and policies.

Signed-off-by: Andreas Auernhammer <hi@aead.dev>
  • Loading branch information
aead committed Aug 31, 2022
1 parent a712a6e commit 6f36f45
Show file tree
Hide file tree
Showing 5 changed files with 316 additions and 372 deletions.
8 changes: 4 additions & 4 deletions kestest/example_test.go
Expand Up @@ -14,8 +14,8 @@ import (
"github.com/minio/kes/kestest"
)

func ExampleServer() {
server := kestest.NewServer()
func ExampleGateway() {
server := kestest.NewGateway()
defer server.Close()

version, err := server.Client().Version(context.Background())
Expand All @@ -28,8 +28,8 @@ func ExampleServer() {
// v0.0.0-dev
}

func ExampleServer_IssueClientCertificate() {
server := kestest.NewServer()
func ExampleGateway_IssueClientCertificate() {
server := kestest.NewGateway()
defer server.Close()

server.Policy().Allow("test-policy",
Expand Down
217 changes: 217 additions & 0 deletions kestest/gateway.go
@@ -0,0 +1,217 @@
// Copyright 2021 - MinIO, Inc. All rights reserved.
// Use of this source code is governed by the AGPLv3
// license that can be found in the LICENSE file.

// Package kestest provides utilities for end-to-end
// KES testing.
package kestest

import (
"crypto"
"crypto/ed25519"
"crypto/rand"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"io"
"math/big"
"net"
"net/http/httptest"
"time"

"github.com/minio/kes"
"github.com/minio/kes/internal/auth"
xhttp "github.com/minio/kes/internal/http"
"github.com/minio/kes/internal/key"
"github.com/minio/kes/internal/log"
"github.com/minio/kes/internal/mem"
"github.com/minio/kes/internal/metric"
)

// NewGateway starts and returns a new Gateway.
// The caller should call Close when finished,
// to shut it down.
func NewGateway() *Gateway {
g := &Gateway{}
g.start()
return g
}

// A Gateway is a KES gateway listening on a system-chosen
// port on the local loopback interface, for use in
// end-to-end tests.
type Gateway struct {
URL string

policies *PolicySet
client *kes.Client

caPrivateKey crypto.PrivateKey
caCertificate *x509.Certificate

server *httptest.Server
}

// Client returns a KES client configured for making requests
// to the Gateway as admin identity.
//
// It is configured to trust the Gateway's TLS test certificate.
func (g *Gateway) Client() *kes.Client { return g.client }

// Policy returns the PolicySet that contains all KES policies
// and identity-policy associations.
func (g *Gateway) Policy() *PolicySet { return g.policies }

// Close shuts down the Gateway and blocks until all outstanding
// requests on this server have completed.
func (g *Gateway) Close() { g.server.Close() }

// IssueClientCertificate returns a new TLS certificate for
// client authentication with the given common name.
//
// The returned certificate is issued by a testing CA that is
// trusted by the Gateway.
func (g *Gateway) IssueClientCertificate(name string) tls.Certificate {
if g.caCertificate == nil || g.caPrivateKey == nil {
g.caPrivateKey, g.caCertificate = newCA()
}
return issueCertificate(name, g.caCertificate, g.caPrivateKey, x509.ExtKeyUsageClientAuth)
}

// CAs returns the Gateway's root CAs.
func (g *Gateway) CAs() *x509.CertPool {
if g.caCertificate == nil || g.caPrivateKey == nil {
g.caPrivateKey, g.caCertificate = newCA()
}

certpool := x509.NewCertPool()
certpool.AddCert(g.caCertificate)
return certpool
}

func (g *Gateway) start() {
var (
rootCAs = g.CAs()
auditLog = log.NewTarget(io.Discard)
errorLog = log.NewTarget(io.Discard)
metrics = metric.New()
adminCert = g.IssueClientCertificate("kestest: admin")
)
g.policies = &PolicySet{
admin: Identify(&adminCert),
policies: make(map[string]*auth.Policy),
identities: make(map[kes.Identity]auth.IdentityInfo),
}

errorLog.Add(metrics.ErrorEventCounter())
auditLog.Add(metrics.AuditEventCounter())
store := key.NewCache(&mem.Store{}, &key.CacheConfig{
Expiry: 30 * time.Second,
ExpiryUnused: 5 * time.Second,
})

serverCert := issueCertificate("kestest: gateway", g.caCertificate, g.caPrivateKey, x509.ExtKeyUsageServerAuth)
g.server = httptest.NewUnstartedServer(xhttp.NewGatewayMux(&xhttp.GatewayConfig{
Keys: store,
Policies: g.policies.policySet(),
Identities: g.policies.identitySet(),
Proxy: nil,
AuditLog: auditLog,
ErrorLog: errorLog,
Metrics: metrics,
}))
g.server.TLS = &tls.Config{
RootCAs: rootCAs,
ClientCAs: rootCAs,
Certificates: []tls.Certificate{serverCert},
ClientAuth: tls.RequireAndVerifyClientCert,
}
g.server.StartTLS()
g.URL = g.server.URL

g.client = kes.NewClientWithConfig(g.URL, &tls.Config{
Certificates: []tls.Certificate{adminCert},
RootCAs: rootCAs,
})
}

func newCA() (crypto.PrivateKey, *x509.Certificate) {
publicKey, privateKey, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
panic(fmt.Sprintf("kestest: failed to generate CA private key: %v", err))
}

serialNumber, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
if err != nil {
panic(fmt.Sprintf("kestest: failed to generate CA certificate serial number: %v", err))
}

template := x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
CommonName: "kestest Root CA",
},
NotBefore: time.Now(),
NotAfter: time.Now().Add(24 * time.Hour),
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageDigitalSignature,
BasicConstraintsValid: true,
IsCA: true,
}
certBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, publicKey, privateKey)
if err != nil {
panic(fmt.Sprintf("kestest: failed to generate CA certificate: %v", err))
}
certificate, err := x509.ParseCertificate(certBytes)
if err != nil {
panic(fmt.Sprintf("kestest: failed to generate CA certificate: %v", err))
}
return privateKey, certificate
}

func issueCertificate(name string, caCert *x509.Certificate, caKey crypto.PrivateKey, extKeyUsage ...x509.ExtKeyUsage) tls.Certificate {
publicKey, privateKey, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
panic(fmt.Sprintf("kestest: failed to generate private/public key pair: %v", err))
}

serialNumber, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
if err != nil {
panic(fmt.Sprintf("kestest: failed to generate certificate serial number: %v", err))
}

template := x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
CommonName: name,
},
NotBefore: time.Now(),
NotAfter: time.Now().Add(24 * time.Hour),
KeyUsage: x509.KeyUsageDigitalSignature,
ExtKeyUsage: extKeyUsage,
IPAddresses: []net.IP{net.IPv4(127, 0, 0, 1), net.IPv6loopback},
DNSNames: []string{"localhost"},
BasicConstraintsValid: true,
}

rawCertificate, err := x509.CreateCertificate(rand.Reader, &template, caCert, publicKey, caKey)
if err != nil {
panic(fmt.Sprintf("kestest: failed to create certificate: %v", err))
}
rawPrivateKey, err := x509.MarshalPKCS8PrivateKey(privateKey)
if err != nil {
panic(fmt.Sprintf("kestest: failed to create certificate: %v", err))
}
certificate, err := tls.X509KeyPair(pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE",
Bytes: rawCertificate,
}), pem.EncodeToMemory(&pem.Block{
Type: "PRIVATE KEY",
Bytes: rawPrivateKey,
}))
if err != nil {
panic(fmt.Sprintf("kestest: failed to create certificate: %v", err))
}
return certificate
}

0 comments on commit 6f36f45

Please sign in to comment.