cloudfoundry/korifi

View on GitHub
api/authorization/testhelpers/auth_provider.go

Summary

Maintainability
A
0 mins
Test Coverage
A
100%
package testhelpers
 
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/json"
"encoding/pem"
"fmt"
"math/big"
"net/http"
"os"
"time"
 
"github.com/golang-jwt/jwt"
"github.com/onsi/gomega"
"github.com/onsi/gomega/ghttp"
"gopkg.in/square/go-jose.v2"
)
 
const audience string = "test-audience"
 
// When configured to work with OIDC the k8s api server calls the
// /.well-known/openid-configuration endpoint of the configurd oidc issuer.
// Later when a token review is created the api server tries to validate the
// token using the public part of the key that the token was signed with. This
// public key is serverd on the jwks_uri endpoint that was advertised by the
// initial request of /.well-known/openid-configuration.
//
// This utility generates JWT tokens and signs them with a signing key, while
// at the same time serving the public part of the signing key to the api
// server.
type AuthProvider struct {
server *ghttp.Server
serverCAPath string
signingKey *rsa.PrivateKey
}
 
func NewAuthProvider() *AuthProvider {
signingKey, err := rsa.GenerateKey(rand.Reader, 2048)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
 
server := ghttp.NewTLSServer()
configureServer(server, signingKey)
 
return &AuthProvider{
server: server,
serverCAPath: writeCAToTempFile(server),
signingKey: signingKey,
}
}
 
func (p *AuthProvider) GenerateJWTToken(subject string, groups ...string) string {
atClaims := jwt.MapClaims{}
atClaims["iss"] = p.server.URL()
atClaims["aud"] = audience
atClaims["sub"] = subject
atClaims["exp"] = time.Now().Add(time.Minute * 15).Unix()
atClaims["groups"] = groups
at := jwt.NewWithClaims(jwt.SigningMethodRS256, atClaims)
token, err := at.SignedString(p.signingKey)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
 
return token
}
 
func writeCAToTempFile(server *ghttp.Server) string {
caDerBytes := server.HTTPTestServer.TLS.Certificates[0].Certificate[0]
 
certOut, err := os.CreateTemp("", "test-oidc-ca")
gomega.Expect(err).NotTo(gomega.HaveOccurred())
 
err = pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: caDerBytes})
gomega.Expect(err).NotTo(gomega.HaveOccurred())
 
gomega.Expect(certOut.Close()).To(gomega.Succeed())
 
return certOut.Name()
}
 
func renderJWKSResponse(signingKey *rsa.PrivateKey) string {
template := &x509.Certificate{
IsCA: true,
BasicConstraintsValid: true,
SubjectKeyId: []byte{1, 2, 3},
SerialNumber: big.NewInt(1234),
Subject: pkix.Name{
Country: []string{"Earth"},
Organization: []string{"Mother Nature"},
},
NotBefore: time.Now(),
NotAfter: time.Now().Add(time.Hour),
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
}
 
signingDerBytes, err := x509.CreateCertificate(rand.Reader, template, template, &signingKey.PublicKey, signingKey)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
cert, err := x509.ParseCertificate(signingDerBytes)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
 
jwks := jose.JSONWebKeySet{
Keys: []jose.JSONWebKey{{
Key: &signingKey.PublicKey,
KeyID: "1",
Use: "sig",
Algorithm: "RS256",
Certificates: []*x509.Certificate{cert},
}},
}
jwksBytes, err := json.MarshalIndent(jwks, "", " ")
gomega.Expect(err).NotTo(gomega.HaveOccurred())
 
return string(jwksBytes)
}
 
func configureServer(server *ghttp.Server, signingKey *rsa.PrivateKey) {
applicationJSONHeader := http.Header{}
applicationJSONHeader.Add("Content-Type", "application/json")
 
server.SetAllowUnhandledRequests(true)
 
server.RouteToHandler(
http.MethodGet,
"/.well-known/openid-configuration",
ghttp.RespondWith(
http.StatusOK,
fmt.Sprintf(`{
"issuer":
"%[1]s",
"jwks_uri":
"%[1]s/jwks.json"
}`, server.URL()),
applicationJSONHeader,
),
)
 
server.RouteToHandler(
http.MethodGet,
"/jwks.json",
ghttp.RespondWith(
http.StatusOK,
renderJWKSResponse(signingKey),
applicationJSONHeader,
),
)
}
 
func (p *AuthProvider) Stop() {
p.server.Close()
gomega.Expect(os.RemoveAll(p.serverCAPath)).To(gomega.Succeed())
}
 
func (p *AuthProvider) APIServerExtraArgs(oidcPrefix string) map[string]string {
return map[string]string{
"oidc-issuer-url": p.server.URL(),
"oidc-client-id": audience,
"oidc-ca-file": p.serverCAPath,
"oidc-username-prefix": oidcPrefix,
"oidc-groups-claim": "groups",
}
}