This commit is contained in:
@@ -0,0 +1,206 @@
|
|||||||
|
package gatewaytls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/pem"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Material struct {
|
||||||
|
ServerTLS *tls.Config
|
||||||
|
ControlTLS *tls.Config
|
||||||
|
CertificateIdentity string
|
||||||
|
}
|
||||||
|
|
||||||
|
func Load(certFile, keyFile, streamingCAFile, controlCAFile, gatewayID, publicIdentity string) (Material, error) {
|
||||||
|
certificate, err := tls.LoadX509KeyPair(certFile, keyFile)
|
||||||
|
if err != nil {
|
||||||
|
return Material{}, fmt.Errorf("load gateway certificate: %w", err)
|
||||||
|
}
|
||||||
|
if len(certificate.Certificate) == 0 {
|
||||||
|
return Material{}, errors.New("gateway certificate chain is empty")
|
||||||
|
}
|
||||||
|
leaf, err := x509.ParseCertificate(certificate.Certificate[0])
|
||||||
|
if err != nil {
|
||||||
|
return Material{}, errors.New("parse gateway leaf certificate")
|
||||||
|
}
|
||||||
|
certificate.Leaf = leaf
|
||||||
|
if now := time.Now(); now.Before(leaf.NotBefore) || now.After(leaf.NotAfter) {
|
||||||
|
return Material{}, errors.New("gateway leaf certificate is not currently valid")
|
||||||
|
}
|
||||||
|
if !hasUsage(leaf, x509.ExtKeyUsageServerAuth) || !hasUsage(leaf, x509.ExtKeyUsageClientAuth) {
|
||||||
|
return Material{}, errors.New("gateway leaf certificate requires ServerAuth and ClientAuth")
|
||||||
|
}
|
||||||
|
if !gatewayURIAllowed(leaf.URIs, gatewayID) {
|
||||||
|
return Material{}, errors.New("gateway leaf certificate URI identity mismatch")
|
||||||
|
}
|
||||||
|
if !validPublicDNSName(publicIdentity) || !slices.Contains(leaf.DNSNames, publicIdentity) || leaf.VerifyHostname(publicIdentity) != nil {
|
||||||
|
return Material{}, errors.New("gateway public identity must match a DNS SAN")
|
||||||
|
}
|
||||||
|
streamingCAs, err := loadCAPool(streamingCAFile)
|
||||||
|
if err != nil {
|
||||||
|
return Material{}, fmt.Errorf("load streaming CA: %w", err)
|
||||||
|
}
|
||||||
|
controlRoots, err := loadControlRootPool(controlCAFile)
|
||||||
|
if err != nil {
|
||||||
|
return Material{}, fmt.Errorf("load control root: %w", err)
|
||||||
|
}
|
||||||
|
digest := sha256.Sum256(leaf.Raw)
|
||||||
|
return Material{
|
||||||
|
ServerTLS: &tls.Config{
|
||||||
|
MinVersion: tls.VersionTLS13, Certificates: []tls.Certificate{certificate},
|
||||||
|
ClientAuth: tls.RequireAndVerifyClientCert, ClientCAs: streamingCAs,
|
||||||
|
},
|
||||||
|
ControlTLS: &tls.Config{
|
||||||
|
MinVersion: tls.VersionTLS13, Certificates: []tls.Certificate{certificate}, RootCAs: controlRoots,
|
||||||
|
},
|
||||||
|
CertificateIdentity: "sha256:" + hex.EncodeToString(digest[:]),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadCAPool(path string) (*x509.CertPool, error) {
|
||||||
|
encoded, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
pool := x509.NewCertPool()
|
||||||
|
count := 0
|
||||||
|
for len(bytes.TrimSpace(encoded)) > 0 {
|
||||||
|
block, rest := pem.Decode(encoded)
|
||||||
|
if block == nil {
|
||||||
|
return nil, errors.New("CA PEM contains invalid data")
|
||||||
|
}
|
||||||
|
encoded = rest
|
||||||
|
if block.Type != "CERTIFICATE" {
|
||||||
|
return nil, errors.New("CA PEM contains a non-certificate block")
|
||||||
|
}
|
||||||
|
certificate, parseErr := x509.ParseCertificate(block.Bytes)
|
||||||
|
if parseErr != nil || !certificate.IsCA || certificate.KeyUsage&x509.KeyUsageCertSign == 0 {
|
||||||
|
return nil, errors.New("CA PEM contains a non-CA certificate")
|
||||||
|
}
|
||||||
|
pool.AddCert(certificate)
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
if count == 0 {
|
||||||
|
return nil, errors.New("CA PEM contains no certificate")
|
||||||
|
}
|
||||||
|
return pool, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadControlRootPool(path string) (*x509.CertPool, error) {
|
||||||
|
encoded, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var certificates []*x509.Certificate
|
||||||
|
for len(bytes.TrimSpace(encoded)) > 0 {
|
||||||
|
block, rest := pem.Decode(encoded)
|
||||||
|
if block == nil {
|
||||||
|
return nil, errors.New("control trust PEM contains invalid data")
|
||||||
|
}
|
||||||
|
encoded = rest
|
||||||
|
if block.Type != "CERTIFICATE" {
|
||||||
|
return nil, errors.New("control trust PEM contains a non-certificate block")
|
||||||
|
}
|
||||||
|
certificate, parseErr := x509.ParseCertificate(block.Bytes)
|
||||||
|
if parseErr != nil {
|
||||||
|
return nil, errors.New("control trust PEM contains an invalid certificate")
|
||||||
|
}
|
||||||
|
certificates = append(certificates, certificate)
|
||||||
|
}
|
||||||
|
if len(certificates) == 0 {
|
||||||
|
return nil, errors.New("control trust PEM contains no certificate")
|
||||||
|
}
|
||||||
|
|
||||||
|
pool := x509.NewCertPool()
|
||||||
|
allCAs := true
|
||||||
|
for _, certificate := range certificates {
|
||||||
|
if !certificate.IsCA || certificate.KeyUsage&x509.KeyUsageCertSign == 0 {
|
||||||
|
allCAs = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if allCAs {
|
||||||
|
for _, certificate := range certificates {
|
||||||
|
pool.AddCert(certificate)
|
||||||
|
}
|
||||||
|
return pool, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
leaf := certificates[0]
|
||||||
|
if len(certificates) != 1 || leaf.IsCA || !hasUsage(leaf, x509.ExtKeyUsageServerAuth) {
|
||||||
|
return nil, errors.New("control trust PEM must contain CAs or one ServerAuth leaf")
|
||||||
|
}
|
||||||
|
pool.AddCert(leaf)
|
||||||
|
return pool, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasUsage(certificate *x509.Certificate, wanted x509.ExtKeyUsage) bool {
|
||||||
|
for _, usage := range certificate.ExtKeyUsage {
|
||||||
|
if usage == wanted {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func gatewayURIAllowed(uris []*url.URL, gatewayID string) bool {
|
||||||
|
if !validUUID(gatewayID) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
prefix := "/gateway/" + gatewayID + "/credential/"
|
||||||
|
for _, uri := range uris {
|
||||||
|
if uri == nil || uri.Scheme != "spiffe" || uri.Host != "versevdi" || uri.User != nil || uri.Opaque != "" || uri.RawPath != "" ||
|
||||||
|
uri.RawQuery != "" || uri.ForceQuery || uri.Fragment != "" || uri.RawFragment != "" || !strings.HasPrefix(uri.Path, prefix) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if validUUID(strings.TrimPrefix(uri.Path, prefix)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func validPublicDNSName(name string) bool {
|
||||||
|
if len(name) == 0 || len(name) > 253 || net.ParseIP(name) != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, label := range strings.Split(name, ".") {
|
||||||
|
if len(label) == 0 || len(label) > 63 || label[0] == '-' || label[len(label)-1] == '-' {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, character := range []byte(label) {
|
||||||
|
if !(character >= 'a' && character <= 'z' || character >= '0' && character <= '9' || character == '-') {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func validUUID(value string) bool {
|
||||||
|
if len(value) != 36 || value != strings.ToLower(value) || value[8] != '-' || value[13] != '-' || value[18] != '-' || value[23] != '-' {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
decoded, err := hex.DecodeString(strings.ReplaceAll(value, "-", ""))
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, b := range decoded {
|
||||||
|
if b != 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
@@ -0,0 +1,474 @@
|
|||||||
|
package gatewaytls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/ed25519"
|
||||||
|
"crypto/elliptic"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
"crypto/x509/pkix"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/pem"
|
||||||
|
"math/big"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
testGatewayID = "7d0d1308-5e50-45ef-984c-4508ca899f5a"
|
||||||
|
testCredentialID = "ba4d93f8-d692-4210-97db-5a561bfb32d3"
|
||||||
|
)
|
||||||
|
|
||||||
|
type testCA struct {
|
||||||
|
certificate *x509.Certificate
|
||||||
|
key *ecdsa.PrivateKey
|
||||||
|
der []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoadSeparatesTrustPoolsAndDerivesGatewayIdentity(t *testing.T) {
|
||||||
|
streamingCA := newTestCA(t, "streaming-ca")
|
||||||
|
controlCA := newTestCA(t, "control-ca")
|
||||||
|
identityCA := newTestCA(t, "identity-ca")
|
||||||
|
uri := mustURL(t, "spiffe://versevdi/gateway/"+testGatewayID+"/credential/"+testCredentialID)
|
||||||
|
leafDER, key := newLeaf(t, identityCA, "gateway.example", []*url.URL{uri}, []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth})
|
||||||
|
certFile, keyFile := writeKeyPair(t, leafDER, identityCA.der, key)
|
||||||
|
streamingFile := writeCertificate(t, "streaming-ca.pem", streamingCA.der)
|
||||||
|
controlFile := writeCertificate(t, "control-ca.pem", controlCA.der)
|
||||||
|
|
||||||
|
material, err := Load(certFile, keyFile, streamingFile, controlFile, testGatewayID, "gateway.example")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Load() error = %v", err)
|
||||||
|
}
|
||||||
|
digest := sha256.Sum256(leafDER)
|
||||||
|
if material.CertificateIdentity != "sha256:"+hex.EncodeToString(digest[:]) {
|
||||||
|
t.Fatalf("certificate identity = %q", material.CertificateIdentity)
|
||||||
|
}
|
||||||
|
if material.ServerTLS.MinVersion != tls.VersionTLS13 || material.ControlTLS.MinVersion != tls.VersionTLS13 || material.ServerTLS.ClientAuth != tls.RequireAndVerifyClientCert ||
|
||||||
|
material.ServerTLS.ClientCAs == nil || material.ControlTLS.RootCAs == nil ||
|
||||||
|
len(material.ServerTLS.Certificates) != 1 || len(material.ControlTLS.Certificates) != 1 {
|
||||||
|
t.Fatalf("TLS material = server:%#v control:%#v", material.ServerTLS, material.ControlTLS)
|
||||||
|
}
|
||||||
|
if got := material.ServerTLS.ClientCAs.Subjects(); len(got) != 1 || string(got[0]) != string(streamingCA.certificate.RawSubject) {
|
||||||
|
t.Fatalf("streaming ClientCAs = %x", got)
|
||||||
|
}
|
||||||
|
if got := material.ControlTLS.RootCAs.Subjects(); len(got) != 1 || string(got[0]) != string(controlCA.certificate.RawSubject) {
|
||||||
|
t.Fatalf("control RootCAs = %x", got)
|
||||||
|
}
|
||||||
|
if string(material.ServerTLS.Certificates[0].Certificate[0]) != string(material.ControlTLS.Certificates[0].Certificate[0]) {
|
||||||
|
t.Fatal("server and control TLS did not use the same gateway leaf")
|
||||||
|
}
|
||||||
|
|
||||||
|
trustedControlLeaf, _ := newLeaf(t, controlCA, "control.example", nil, []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth})
|
||||||
|
parsedTrusted, _ := x509.ParseCertificate(trustedControlLeaf)
|
||||||
|
if _, err := parsedTrusted.Verify(x509.VerifyOptions{Roots: material.ControlTLS.RootCAs, DNSName: "control.example"}); err != nil {
|
||||||
|
t.Fatalf("control CA did not verify control server: %v", err)
|
||||||
|
}
|
||||||
|
wrongControlLeaf, _ := newLeaf(t, streamingCA, "control.example", nil, []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth})
|
||||||
|
parsedWrong, _ := x509.ParseCertificate(wrongControlLeaf)
|
||||||
|
if _, err := parsedWrong.Verify(x509.VerifyOptions{Roots: material.ControlTLS.RootCAs, DNSName: "control.example"}); err == nil {
|
||||||
|
t.Fatal("control RootCAs trusted the streaming CA")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoadTrustsExactSelfSignedControlLeaf(t *testing.T) {
|
||||||
|
streamingCA := newTestCA(t, "streaming-ca")
|
||||||
|
identityCA := newTestCA(t, "identity-ca")
|
||||||
|
uri := mustURL(t, "spiffe://versevdi/gateway/"+testGatewayID+"/credential/"+testCredentialID)
|
||||||
|
gatewayLeaf, gatewayKey := newLeaf(t, identityCA, "gateway.example", []*url.URL{uri}, []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth})
|
||||||
|
certFile, keyFile := writeKeyPair(t, gatewayLeaf, identityCA.der, gatewayKey)
|
||||||
|
streamingFile := writeCertificate(t, "streaming-ca.pem", streamingCA.der)
|
||||||
|
|
||||||
|
controlLeaf, controlKey := newSelfSignedServerLeaf(t, net.ParseIP("127.0.0.1"))
|
||||||
|
controlFile := writeCertificate(t, "control-leaf.pem", controlLeaf)
|
||||||
|
material, err := Load(certFile, keyFile, streamingFile, controlFile, testGatewayID, "gateway.example")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Load() error = %v", err)
|
||||||
|
}
|
||||||
|
if material.ControlTLS.MinVersion != tls.VersionTLS13 {
|
||||||
|
t.Fatalf("ControlTLS.MinVersion = %d, want TLS 1.3", material.ControlTLS.MinVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
clientCAs := x509.NewCertPool()
|
||||||
|
clientCAs.AddCert(identityCA.certificate)
|
||||||
|
trusted := newControlServer(t, controlLeaf, controlKey, clientCAs)
|
||||||
|
defer trusted.Close()
|
||||||
|
response, err := (&http.Client{Transport: &http.Transport{TLSClientConfig: material.ControlTLS}}).Get(trusted.URL)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("pinned self-signed control leaf handshake: %v", err)
|
||||||
|
}
|
||||||
|
response.Body.Close()
|
||||||
|
if response.StatusCode != http.StatusNoContent {
|
||||||
|
t.Fatalf("trusted control response status = %d", response.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
wrongLeaf, wrongKey := newSelfSignedServerLeaf(t, net.ParseIP("127.0.0.1"))
|
||||||
|
wrong := newControlServer(t, wrongLeaf, wrongKey, clientCAs)
|
||||||
|
defer wrong.Close()
|
||||||
|
if _, err := (&http.Client{Transport: &http.Transport{TLSClientConfig: material.ControlTLS}}).Get(wrong.URL); err == nil {
|
||||||
|
t.Fatal("different self-signed leaf with the same SAN completed handshake")
|
||||||
|
}
|
||||||
|
|
||||||
|
mismatchedLeaf, mismatchedKey := newSelfSignedServerLeaf(t, net.ParseIP("127.0.0.2"))
|
||||||
|
mismatchedFile := writeCertificate(t, "mismatched-control-leaf.pem", mismatchedLeaf)
|
||||||
|
mismatchedMaterial, err := Load(certFile, keyFile, streamingFile, mismatchedFile, testGatewayID, "gateway.example")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Load() mismatched control leaf error = %v", err)
|
||||||
|
}
|
||||||
|
mismatched := newControlServer(t, mismatchedLeaf, mismatchedKey, clientCAs)
|
||||||
|
defer mismatched.Close()
|
||||||
|
if _, err := (&http.Client{Transport: &http.Transport{TLSClientConfig: mismatchedMaterial.ControlTLS}}).Get(mismatched.URL); err == nil {
|
||||||
|
t.Fatal("control leaf without the request hostname/SAN completed handshake")
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := Load(certFile, keyFile, controlFile, controlFile, testGatewayID, "gateway.example"); err == nil {
|
||||||
|
t.Fatal("Load() accepted a non-CA leaf as streaming trust")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoadTrustsExactCAIssuedControlLeaf(t *testing.T) {
|
||||||
|
streamingCA := newTestCA(t, "streaming-ca")
|
||||||
|
controlCA := newTestCA(t, "control-ca")
|
||||||
|
identityCA := newTestCA(t, "identity-ca")
|
||||||
|
uri := mustURL(t, "spiffe://versevdi/gateway/"+testGatewayID+"/credential/"+testCredentialID)
|
||||||
|
gatewayLeaf, gatewayKey := newLeaf(t, identityCA, "gateway.example", []*url.URL{uri}, []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth})
|
||||||
|
certFile, keyFile := writeKeyPair(t, gatewayLeaf, identityCA.der, gatewayKey)
|
||||||
|
streamingFile := writeCertificate(t, "streaming-ca.pem", streamingCA.der)
|
||||||
|
|
||||||
|
activeLeaf, activeKey := newCAIssuedControlLeaf(t, controlCA, net.ParseIP("127.0.0.1"))
|
||||||
|
controlFile := writeCertificate(t, "control-leaf.pem", activeLeaf)
|
||||||
|
material, err := Load(certFile, keyFile, streamingFile, controlFile, testGatewayID, "gateway.example")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Load() error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
clientCAs := x509.NewCertPool()
|
||||||
|
clientCAs.AddCert(identityCA.certificate)
|
||||||
|
trusted := newControlServer(t, activeLeaf, activeKey, clientCAs, controlCA.der)
|
||||||
|
defer trusted.Close()
|
||||||
|
response, err := (&http.Client{Transport: &http.Transport{TLSClientConfig: material.ControlTLS}}).Get(trusted.URL)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("pinned CA-issued control leaf handshake: %v", err)
|
||||||
|
}
|
||||||
|
response.Body.Close()
|
||||||
|
if response.TLS == nil || response.TLS.Version != tls.VersionTLS13 {
|
||||||
|
t.Fatalf("pinned control leaf TLS version = %v, want TLS 1.3", response.TLS)
|
||||||
|
}
|
||||||
|
if response.StatusCode != http.StatusNoContent {
|
||||||
|
t.Fatalf("trusted control response status = %d", response.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
siblingLeaf, siblingKey := newCAIssuedControlLeaf(t, controlCA, net.ParseIP("127.0.0.1"))
|
||||||
|
sibling := newControlServer(t, siblingLeaf, siblingKey, clientCAs, controlCA.der)
|
||||||
|
defer sibling.Close()
|
||||||
|
if _, err := (&http.Client{Transport: &http.Transport{TLSClientConfig: material.ControlTLS}}).Get(sibling.URL); err == nil {
|
||||||
|
t.Fatal("sibling control leaf from the same issuer and SAN completed handshake")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoadRejectsControlLeafBundlesAndNonServerAuth(t *testing.T) {
|
||||||
|
streamingCA := newTestCA(t, "streaming-ca")
|
||||||
|
controlCA := newTestCA(t, "control-ca")
|
||||||
|
identityCA := newTestCA(t, "identity-ca")
|
||||||
|
uri := mustURL(t, "spiffe://versevdi/gateway/"+testGatewayID+"/credential/"+testCredentialID)
|
||||||
|
gatewayLeaf, gatewayKey := newLeaf(t, identityCA, "gateway.example", []*url.URL{uri}, []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth})
|
||||||
|
certFile, keyFile := writeKeyPair(t, gatewayLeaf, identityCA.der, gatewayKey)
|
||||||
|
streamingFile := writeCertificate(t, "streaming-ca.pem", streamingCA.der)
|
||||||
|
|
||||||
|
serverLeaf, _ := newCAIssuedControlLeaf(t, controlCA, net.ParseIP("127.0.0.1"))
|
||||||
|
nonServerLeaf, _ := newLeaf(t, controlCA, "control.example", nil, []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth})
|
||||||
|
for _, test := range []struct {
|
||||||
|
name string
|
||||||
|
certificates [][]byte
|
||||||
|
}{
|
||||||
|
{"leaf and issuer chain", [][]byte{serverLeaf, controlCA.der}},
|
||||||
|
{"non ServerAuth leaf", [][]byte{nonServerLeaf}},
|
||||||
|
} {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
controlFile := writeCertificateBundle(t, "control.pem", test.certificates...)
|
||||||
|
if _, err := Load(certFile, keyFile, streamingFile, controlFile, testGatewayID, "gateway.example"); err == nil {
|
||||||
|
t.Fatal("Load() accepted invalid control trust material")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoadRejectsWrongPublicNameURIAndLeafUsage(t *testing.T) {
|
||||||
|
streamingCA := newTestCA(t, "streaming-ca")
|
||||||
|
controlCA := newTestCA(t, "control-ca")
|
||||||
|
identityCA := newTestCA(t, "identity-ca")
|
||||||
|
streamingFile := writeCertificate(t, "streaming-ca.pem", streamingCA.der)
|
||||||
|
controlFile := writeCertificate(t, "control-ca.pem", controlCA.der)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
uriGatewayID string
|
||||||
|
publicIdentity string
|
||||||
|
leafDNSName string
|
||||||
|
usages []x509.ExtKeyUsage
|
||||||
|
}{
|
||||||
|
{"wrong public name", testGatewayID, "other.example", "gateway.example", []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}},
|
||||||
|
{"noncanonical public identity", testGatewayID, " gateway.example ", "gateway.example", []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}},
|
||||||
|
{"wildcard is not an exact public identity", testGatewayID, "gateway.example", "*.example", []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}},
|
||||||
|
{"wrong URI gateway", "c1f5d25a-47c6-44ed-b9be-e0e8e87d4ae2", "gateway.example", "gateway.example", []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}},
|
||||||
|
{"missing client auth", testGatewayID, "gateway.example", "gateway.example", []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}},
|
||||||
|
}
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
uri := mustURL(t, "spiffe://versevdi/gateway/"+test.uriGatewayID+"/credential/"+testCredentialID)
|
||||||
|
leafDER, key := newLeaf(t, identityCA, test.leafDNSName, []*url.URL{uri}, test.usages)
|
||||||
|
certFile, keyFile := writeKeyPair(t, leafDER, identityCA.der, key)
|
||||||
|
if _, err := Load(certFile, keyFile, streamingFile, controlFile, testGatewayID, test.publicIdentity); err == nil {
|
||||||
|
t.Fatal("Load() accepted invalid gateway certificate identity")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoadRejectsNonCanonicalPublicDNSIdentity(t *testing.T) {
|
||||||
|
streamingCA := newTestCA(t, "streaming-ca")
|
||||||
|
controlCA := newTestCA(t, "control-ca")
|
||||||
|
identityCA := newTestCA(t, "identity-ca")
|
||||||
|
streamingFile := writeCertificate(t, "streaming-ca.pem", streamingCA.der)
|
||||||
|
controlFile := writeCertificate(t, "control-ca.pem", controlCA.der)
|
||||||
|
uri := mustURL(t, "spiffe://versevdi/gateway/"+testGatewayID+"/credential/"+testCredentialID)
|
||||||
|
tooLongLabel := strings.Repeat("a", 64) + ".example"
|
||||||
|
tooLongName := strings.Repeat("a", 63) + "." + strings.Repeat("b", 63) + "." + strings.Repeat("c", 63) + "." + strings.Repeat("d", 62)
|
||||||
|
|
||||||
|
for _, test := range []struct {
|
||||||
|
name string
|
||||||
|
leafDNSName string
|
||||||
|
publicIdentity string
|
||||||
|
}{
|
||||||
|
{"uppercase exact SAN", "Gateway.example", "Gateway.example"},
|
||||||
|
{"empty label", "gateway..example", "gateway..example"},
|
||||||
|
{"leading hyphen", "-gateway.example", "-gateway.example"},
|
||||||
|
{"trailing hyphen", "gateway-.example", "gateway-.example"},
|
||||||
|
{"wildcard", "*.example", "*.example"},
|
||||||
|
{"label exceeds 63 bytes", tooLongLabel, tooLongLabel},
|
||||||
|
{"name exceeds 253 bytes", tooLongName, tooLongName},
|
||||||
|
{"IP address", "127.0.0.1", "127.0.0.1"},
|
||||||
|
{"port", "gateway.example:443", "gateway.example:443"},
|
||||||
|
{"path", "gateway.example/path", "gateway.example/path"},
|
||||||
|
{"whitespace", "gateway .example", "gateway .example"},
|
||||||
|
{"trailing dot", "gateway.example.", "gateway.example."},
|
||||||
|
{"Unicode", "gateway.example", "gäteway.example"},
|
||||||
|
} {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
leafDER, key := newLeaf(t, identityCA, test.leafDNSName, []*url.URL{uri}, []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth})
|
||||||
|
certFile, keyFile := writeKeyPair(t, leafDER, identityCA.der, key)
|
||||||
|
if _, err := Load(certFile, keyFile, streamingFile, controlFile, testGatewayID, test.publicIdentity); err == nil {
|
||||||
|
t.Fatal("Load() accepted a noncanonical public DNS identity")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoadAcceptsCanonicalPublicDNSIdentity(t *testing.T) {
|
||||||
|
streamingCA := newTestCA(t, "streaming-ca")
|
||||||
|
controlCA := newTestCA(t, "control-ca")
|
||||||
|
identityCA := newTestCA(t, "identity-ca")
|
||||||
|
streamingFile := writeCertificate(t, "streaming-ca.pem", streamingCA.der)
|
||||||
|
controlFile := writeCertificate(t, "control-ca.pem", controlCA.der)
|
||||||
|
uri := mustURL(t, "spiffe://versevdi/gateway/"+testGatewayID+"/credential/"+testCredentialID)
|
||||||
|
|
||||||
|
for _, publicIdentity := range []string{"gateway.example", "gateway"} {
|
||||||
|
t.Run(publicIdentity, func(t *testing.T) {
|
||||||
|
leafDER, key := newLeaf(t, identityCA, publicIdentity, []*url.URL{uri}, []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth})
|
||||||
|
certFile, keyFile := writeKeyPair(t, leafDER, identityCA.der, key)
|
||||||
|
if _, err := Load(certFile, keyFile, streamingFile, controlFile, testGatewayID, publicIdentity); err != nil {
|
||||||
|
t.Fatalf("Load() error = %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoadRejectsLegacyAndNoncanonicalGatewayURIs(t *testing.T) {
|
||||||
|
streamingCA := newTestCA(t, "streaming-ca")
|
||||||
|
controlCA := newTestCA(t, "control-ca")
|
||||||
|
identityCA := newTestCA(t, "identity-ca")
|
||||||
|
streamingFile := writeCertificate(t, "streaming-ca.pem", streamingCA.der)
|
||||||
|
controlFile := writeCertificate(t, "control-ca.pem", controlCA.der)
|
||||||
|
canonical := "spiffe://versevdi/gateway/" + testGatewayID + "/credential/" + testCredentialID
|
||||||
|
|
||||||
|
for _, test := range []struct {
|
||||||
|
name string
|
||||||
|
uri string
|
||||||
|
}{
|
||||||
|
{"legacy URN", "urn:versevdi:gateway:" + testGatewayID + ":credential:" + testCredentialID},
|
||||||
|
{"opaque URI", "spiffe:gateway/" + testGatewayID + "/credential/" + testCredentialID},
|
||||||
|
{"leading path slash", "spiffe://versevdi//gateway/" + testGatewayID + "/credential/" + testCredentialID},
|
||||||
|
{"trailing path slash", canonical + "/"},
|
||||||
|
{"percent encoded credential", "spiffe://versevdi/gateway/" + testGatewayID + "/credential/%62a4d93f8-d692-4210-97db-5a561bfb32d3"},
|
||||||
|
{"force query", canonical + "?"},
|
||||||
|
{"query", canonical + "?version=1"},
|
||||||
|
{"fragment", canonical + "#fragment"},
|
||||||
|
{"user", "spiffe://gateway@versevdi/gateway/" + testGatewayID + "/credential/" + testCredentialID},
|
||||||
|
} {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
uri := mustURL(t, test.uri)
|
||||||
|
leafDER, key := newLeaf(t, identityCA, "gateway.example", []*url.URL{uri}, []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth})
|
||||||
|
certFile, keyFile := writeKeyPair(t, leafDER, identityCA.der, key)
|
||||||
|
if _, err := Load(certFile, keyFile, streamingFile, controlFile, testGatewayID, "gateway.example"); err == nil {
|
||||||
|
t.Fatal("Load() accepted a legacy or noncanonical gateway URI")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("uppercase UUIDs", func(t *testing.T) {
|
||||||
|
gatewayID := strings.ToUpper(testGatewayID)
|
||||||
|
uri := mustURL(t, "spiffe://versevdi/gateway/"+gatewayID+"/credential/"+strings.ToUpper(testCredentialID))
|
||||||
|
leafDER, key := newLeaf(t, identityCA, "gateway.example", []*url.URL{uri}, []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth})
|
||||||
|
certFile, keyFile := writeKeyPair(t, leafDER, identityCA.der, key)
|
||||||
|
if _, err := Load(certFile, keyFile, streamingFile, controlFile, gatewayID, "gateway.example"); err == nil {
|
||||||
|
t.Fatal("Load() accepted noncanonical uppercase UUIDs")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTestCA(t *testing.T, commonName string) testCA {
|
||||||
|
t.Helper()
|
||||||
|
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
template := &x509.Certificate{
|
||||||
|
SerialNumber: big.NewInt(time.Now().UnixNano()), Subject: pkix.Name{CommonName: commonName},
|
||||||
|
NotBefore: time.Now().Add(-time.Hour), NotAfter: time.Now().Add(time.Hour),
|
||||||
|
IsCA: true, BasicConstraintsValid: true, KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageDigitalSignature,
|
||||||
|
}
|
||||||
|
der, err := x509.CreateCertificate(rand.Reader, template, template, &key.PublicKey, key)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
certificate, err := x509.ParseCertificate(der)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
return testCA{certificate: certificate, key: key, der: der}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newLeaf(t *testing.T, ca testCA, dnsName string, uris []*url.URL, usages []x509.ExtKeyUsage) ([]byte, ed25519.PrivateKey) {
|
||||||
|
t.Helper()
|
||||||
|
publicKey, key, err := ed25519.GenerateKey(rand.Reader)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
template := &x509.Certificate{
|
||||||
|
SerialNumber: big.NewInt(time.Now().UnixNano()), Subject: pkix.Name{CommonName: dnsName}, DNSNames: []string{dnsName}, URIs: uris,
|
||||||
|
NotBefore: time.Now().Add(-time.Hour), NotAfter: time.Now().Add(time.Hour),
|
||||||
|
BasicConstraintsValid: true, KeyUsage: x509.KeyUsageDigitalSignature, ExtKeyUsage: usages,
|
||||||
|
}
|
||||||
|
der, err := x509.CreateCertificate(rand.Reader, template, ca.certificate, publicKey, ca.key)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
return der, key
|
||||||
|
}
|
||||||
|
|
||||||
|
func newSelfSignedServerLeaf(t *testing.T, ip net.IP) ([]byte, *ecdsa.PrivateKey) {
|
||||||
|
t.Helper()
|
||||||
|
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
template := &x509.Certificate{
|
||||||
|
SerialNumber: big.NewInt(time.Now().UnixNano()), Subject: pkix.Name{CommonName: "control.example"}, IPAddresses: []net.IP{ip},
|
||||||
|
NotBefore: time.Now().Add(-time.Hour), NotAfter: time.Now().Add(time.Hour),
|
||||||
|
BasicConstraintsValid: true, KeyUsage: x509.KeyUsageDigitalSignature, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||||
|
}
|
||||||
|
der, err := x509.CreateCertificate(rand.Reader, template, template, &key.PublicKey, key)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
return der, key
|
||||||
|
}
|
||||||
|
|
||||||
|
func newCAIssuedControlLeaf(t *testing.T, ca testCA, ip net.IP) ([]byte, ed25519.PrivateKey) {
|
||||||
|
t.Helper()
|
||||||
|
publicKey, key, err := ed25519.GenerateKey(rand.Reader)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
template := &x509.Certificate{
|
||||||
|
SerialNumber: big.NewInt(time.Now().UnixNano()), Subject: pkix.Name{CommonName: "control.example"}, IPAddresses: []net.IP{ip},
|
||||||
|
NotBefore: time.Now().Add(-time.Hour), NotAfter: time.Now().Add(time.Hour),
|
||||||
|
BasicConstraintsValid: true, KeyUsage: x509.KeyUsageDigitalSignature, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||||
|
}
|
||||||
|
der, err := x509.CreateCertificate(rand.Reader, template, ca.certificate, publicKey, ca.key)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
return der, key
|
||||||
|
}
|
||||||
|
|
||||||
|
func newControlServer(t *testing.T, leaf []byte, key any, clientCAs *x509.CertPool, chain ...[]byte) *httptest.Server {
|
||||||
|
t.Helper()
|
||||||
|
server := httptest.NewUnstartedServer(http.HandlerFunc(func(response http.ResponseWriter, request *http.Request) {
|
||||||
|
if request.TLS == nil || len(request.TLS.PeerCertificates) == 0 {
|
||||||
|
http.Error(response, "mTLS required", http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.WriteHeader(http.StatusNoContent)
|
||||||
|
}))
|
||||||
|
certificates := append([][]byte{leaf}, chain...)
|
||||||
|
server.TLS = &tls.Config{
|
||||||
|
MinVersion: tls.VersionTLS13, Certificates: []tls.Certificate{{Certificate: certificates, PrivateKey: key}},
|
||||||
|
ClientAuth: tls.RequireAndVerifyClientCert, ClientCAs: clientCAs,
|
||||||
|
}
|
||||||
|
server.StartTLS()
|
||||||
|
return server
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeKeyPair(t *testing.T, leafDER, issuerDER []byte, key ed25519.PrivateKey) (string, string) {
|
||||||
|
t.Helper()
|
||||||
|
directory := t.TempDir()
|
||||||
|
certFile := filepath.Join(directory, "gateway.pem")
|
||||||
|
keyFile := filepath.Join(directory, "gateway-key.pem")
|
||||||
|
certificatePEM := append(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: leafDER}), pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: issuerDER})...)
|
||||||
|
privateDER, err := x509.MarshalPKCS8PrivateKey(key)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(certFile, certificatePEM, 0o600); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(keyFile, pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: privateDER}), 0o600); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
return certFile, keyFile
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeCertificate(t *testing.T, name string, der []byte) string {
|
||||||
|
return writeCertificateBundle(t, name, der)
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeCertificateBundle(t *testing.T, name string, certificates ...[]byte) string {
|
||||||
|
t.Helper()
|
||||||
|
path := filepath.Join(t.TempDir(), name)
|
||||||
|
var encoded []byte
|
||||||
|
for _, certificate := range certificates {
|
||||||
|
encoded = append(encoded, pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certificate})...)
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(path, encoded, 0o600); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
func mustURL(t *testing.T, value string) *url.URL {
|
||||||
|
t.Helper()
|
||||||
|
parsed, err := url.Parse(value)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
return parsed
|
||||||
|
}
|
||||||
+11
-29
@@ -2,14 +2,11 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
|
||||||
"crypto/x509"
|
|
||||||
"errors"
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
@@ -17,6 +14,7 @@ import (
|
|||||||
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"git.sechmachine.io.vn/sechmachine/VerseVDI-Data-Plane/cmd/internal/gatewaytls"
|
||||||
"git.sechmachine.io.vn/sechmachine/VerseVDI-Data-Plane/gateway"
|
"git.sechmachine.io.vn/sechmachine/VerseVDI-Data-Plane/gateway"
|
||||||
protocol "git.sechmachine.io.vn/sechmachine/VerseVDI-Protocol/gen/go/protocol"
|
protocol "git.sechmachine.io.vn/sechmachine/VerseVDI-Protocol/gen/go/protocol"
|
||||||
)
|
)
|
||||||
@@ -28,20 +26,20 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func run() error {
|
func run() error {
|
||||||
var listen, advertiseAddress, controlPlane, certFile, keyFile, clientCAFile string
|
var listen, advertiseAddress, controlPlane, certFile, keyFile, streamingCAFile, controlCAFile string
|
||||||
var gatewayID, instanceIdentity, certificateIdentity, publicIdentity string
|
var gatewayID, instanceIdentity, publicIdentity string
|
||||||
flag.StringVar(&listen, "listen", "0.0.0.0:443", "gateway QUIC listen address")
|
flag.StringVar(&listen, "listen", "0.0.0.0:443", "gateway QUIC listen address")
|
||||||
flag.StringVar(&advertiseAddress, "advertise-address", "", "client-visible gateway address host:port")
|
flag.StringVar(&advertiseAddress, "advertise-address", "", "client-visible gateway address host:port")
|
||||||
flag.StringVar(&controlPlane, "control-plane", "", "Connection Server HTTPS base URL")
|
flag.StringVar(&controlPlane, "control-plane", "", "Connection Server HTTPS base URL")
|
||||||
flag.StringVar(&certFile, "cert", "", "gateway certificate PEM")
|
flag.StringVar(&certFile, "cert", "", "gateway certificate PEM")
|
||||||
flag.StringVar(&keyFile, "key", "", "gateway private key PEM")
|
flag.StringVar(&keyFile, "key", "", "gateway private key PEM")
|
||||||
flag.StringVar(&clientCAFile, "client-ca", "", "Connection Server/client CA PEM")
|
flag.StringVar(&streamingCAFile, "streaming-ca", "", "CA PEM for QUIC streaming clients")
|
||||||
|
flag.StringVar(&controlCAFile, "control-ca", "", "CA bundle or exact Server leaf PEM")
|
||||||
flag.StringVar(&gatewayID, "gateway-id", "", "stable gateway identifier")
|
flag.StringVar(&gatewayID, "gateway-id", "", "stable gateway identifier")
|
||||||
flag.StringVar(&instanceIdentity, "instance-identity", "", "gateway instance identity")
|
flag.StringVar(&instanceIdentity, "instance-identity", "", "gateway instance identity")
|
||||||
flag.StringVar(&certificateIdentity, "certificate-identity", "", "gateway certificate identity")
|
flag.StringVar(&publicIdentity, "public-identity", "", "gateway DNS identity from the certificate SAN")
|
||||||
flag.StringVar(&publicIdentity, "public-identity", "gateway", "gateway public identity")
|
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
for name, value := range map[string]string{"control-plane": controlPlane, "advertise-address": advertiseAddress, "cert": certFile, "key": keyFile, "client-ca": clientCAFile, "gateway-id": gatewayID, "instance-identity": instanceIdentity, "certificate-identity": certificateIdentity} {
|
for name, value := range map[string]string{"control-plane": controlPlane, "advertise-address": advertiseAddress, "cert": certFile, "key": keyFile, "streaming-ca": streamingCAFile, "control-ca": controlCAFile, "gateway-id": gatewayID, "instance-identity": instanceIdentity, "public-identity": publicIdentity} {
|
||||||
if value == "" {
|
if value == "" {
|
||||||
return fmt.Errorf("-%s is required", name)
|
return fmt.Errorf("-%s is required", name)
|
||||||
}
|
}
|
||||||
@@ -49,20 +47,20 @@ func run() error {
|
|||||||
if err := validateAdvertisedAddress(advertiseAddress); err != nil {
|
if err := validateAdvertisedAddress(advertiseAddress); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
serverTLS, clientTLS, err := loadTLS(certFile, keyFile, clientCAFile)
|
tlsMaterial, err := gatewaytls.Load(certFile, keyFile, streamingCAFile, controlCAFile, gatewayID, publicIdentity)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
transport := &http.Transport{TLSClientConfig: clientTLS}
|
transport := &http.Transport{TLSClientConfig: tlsMaterial.ControlTLS}
|
||||||
controlPlaneClient := gateway.NewControlPlaneClient(controlPlane, &http.Client{Transport: transport, Timeout: 5 * time.Second})
|
controlPlaneClient := gateway.NewControlPlaneClient(controlPlane, &http.Client{Transport: transport, Timeout: 5 * time.Second})
|
||||||
provider := gateway.NewApolloAdapter(gateway.NewNativeApolloBackend(), gateway.ProviderIdentity{})
|
provider := gateway.NewApolloAdapter(gateway.NewNativeApolloBackend(), gateway.ProviderIdentity{})
|
||||||
capabilities := gateway.DefaultCapabilities()
|
capabilities := gateway.DefaultCapabilities()
|
||||||
features := gateway.DefaultFeatures()
|
features := gateway.DefaultFeatures()
|
||||||
server, err := gateway.NewServer(gateway.ServerConfig{ListenAddress: listen, TLSConfig: serverTLS, GatewayID: gatewayID, Features: features, Capabilities: capabilities, ProviderCapabilities: capabilities, Admission: controlPlaneClient, ProviderStateReporter: controlPlaneClient, ClipboardAuditReporter: controlPlaneClient, Provider: provider, PacerKbps: 100000})
|
server, err := gateway.NewServer(gateway.ServerConfig{ListenAddress: listen, TLSConfig: tlsMaterial.ServerTLS, GatewayID: gatewayID, Features: features, Capabilities: capabilities, ProviderCapabilities: capabilities, Admission: controlPlaneClient, ProviderStateReporter: controlPlaneClient, ClipboardAuditReporter: controlPlaneClient, Provider: provider, PacerKbps: 100000})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
registration := protocol.GatewayRegistration{Version: "1", GatewayID: gatewayID, InstanceIdentity: instanceIdentity, CertificateIdentity: certificateIdentity, PublicIdentity: publicIdentity, Address: advertiseAddress, ProviderIdentity: "server-derived", ProtocolMinVersion: 1, ProtocolMaxVersion: 1, ConnectionCapacity: 8, BandwidthCapacityKbps: 100000, Features: features, Capabilities: capabilities}
|
registration := protocol.GatewayRegistration{Version: "1", GatewayID: gatewayID, InstanceIdentity: instanceIdentity, CertificateIdentity: tlsMaterial.CertificateIdentity, PublicIdentity: publicIdentity, Address: advertiseAddress, ProviderIdentity: "server-derived", ProtocolMinVersion: 1, ProtocolMaxVersion: 1, ConnectionCapacity: 8, BandwidthCapacityKbps: 100000, Features: features, Capabilities: capabilities}
|
||||||
if _, err := controlPlaneClient.Register(context.Background(), registration); err != nil {
|
if _, err := controlPlaneClient.Register(context.Background(), registration); err != nil {
|
||||||
_ = server.Close()
|
_ = server.Close()
|
||||||
return err
|
return err
|
||||||
@@ -183,22 +181,6 @@ func providerStateName(value uint64) string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadTLS(certFile, keyFile, clientCAFile string) (*tls.Config, *tls.Config, error) {
|
|
||||||
certificate, err := tls.LoadX509KeyPair(certFile, keyFile)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
caBytes, err := os.ReadFile(clientCAFile)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
clientCAs := x509.NewCertPool()
|
|
||||||
if !clientCAs.AppendCertsFromPEM(caBytes) {
|
|
||||||
return nil, nil, errors.New("client CA PEM contains no certificate")
|
|
||||||
}
|
|
||||||
return &tls.Config{MinVersion: tls.VersionTLS13, Certificates: []tls.Certificate{certificate}, ClientAuth: tls.RequireAndVerifyClientCert, ClientCAs: clientCAs}, &tls.Config{MinVersion: tls.VersionTLS13, Certificates: []tls.Certificate{certificate}, RootCAs: clientCAs}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseProviderIdentity(value string) (gateway.ProviderIdentity, error) {
|
func parseProviderIdentity(value string) (gateway.ProviderIdentity, error) {
|
||||||
uniqueID, fingerprint, ok := strings.Cut(value, "#")
|
uniqueID, fingerprint, ok := strings.Cut(value, "#")
|
||||||
if !ok || uniqueID == "" || fingerprint == "" {
|
if !ok || uniqueID == "" || fingerprint == "" {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ module git.sechmachine.io.vn/sechmachine/VerseVDI-Data-Plane
|
|||||||
go 1.26.5
|
go 1.26.5
|
||||||
|
|
||||||
require (
|
require (
|
||||||
git.sechmachine.io.vn/sechmachine/VerseVDI-Protocol v1.0.0-phase3d-macos-rc.1
|
git.sechmachine.io.vn/sechmachine/VerseVDI-Protocol v1.0.0-phase3d-macos-rc.2
|
||||||
github.com/quic-go/quic-go v0.61.0
|
github.com/quic-go/quic-go v0.61.0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ git.sechmachine.io.vn/sechmachine/VerseVDI-Protocol v1.0.0-phase3c-gateway-rc.10
|
|||||||
git.sechmachine.io.vn/sechmachine/VerseVDI-Protocol v1.0.0-phase3c-gateway-rc.10/go.mod h1:7PhFIDhjtr20btWoEb2GqB+7dBpzJt43olrnHVutWoc=
|
git.sechmachine.io.vn/sechmachine/VerseVDI-Protocol v1.0.0-phase3c-gateway-rc.10/go.mod h1:7PhFIDhjtr20btWoEb2GqB+7dBpzJt43olrnHVutWoc=
|
||||||
git.sechmachine.io.vn/sechmachine/VerseVDI-Protocol v1.0.0-phase3d-macos-rc.1 h1:uCH+etSQtzqfhhRP3dWdp6aIDTU55G7r1UEOAJfWsGw=
|
git.sechmachine.io.vn/sechmachine/VerseVDI-Protocol v1.0.0-phase3d-macos-rc.1 h1:uCH+etSQtzqfhhRP3dWdp6aIDTU55G7r1UEOAJfWsGw=
|
||||||
git.sechmachine.io.vn/sechmachine/VerseVDI-Protocol v1.0.0-phase3d-macos-rc.1/go.mod h1:7PhFIDhjtr20btWoEb2GqB+7dBpzJt43olrnHVutWoc=
|
git.sechmachine.io.vn/sechmachine/VerseVDI-Protocol v1.0.0-phase3d-macos-rc.1/go.mod h1:7PhFIDhjtr20btWoEb2GqB+7dBpzJt43olrnHVutWoc=
|
||||||
|
git.sechmachine.io.vn/sechmachine/VerseVDI-Protocol v1.0.0-phase3d-macos-rc.2 h1:UdkgLXQ2fWFCTGcMblZHXhvQbDx2wfTy4eHyEvav72U=
|
||||||
|
git.sechmachine.io.vn/sechmachine/VerseVDI-Protocol v1.0.0-phase3d-macos-rc.2/go.mod h1:7PhFIDhjtr20btWoEb2GqB+7dBpzJt43olrnHVutWoc=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
|||||||
Reference in New Issue
Block a user