mirror of
https://github.com/hanzokms/kmip.git
synced 2026-10-30 20:33:03 +00:00
254 lines
6.5 KiB
Go
254 lines
6.5 KiB
Go
package kmip
|
|
|
|
import (
|
|
"context"
|
|
"crypto"
|
|
"crypto/ecdsa"
|
|
"crypto/rsa"
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"encoding/json"
|
|
"encoding/pem"
|
|
"fmt"
|
|
"log"
|
|
"net"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/go-resty/resty/v2"
|
|
kms "github.com/hanzokms/go-sdk"
|
|
kmsErrors "github.com/hanzokms/go-sdk/packages/errors"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
type CertificateSet struct {
|
|
ServerKey crypto.PrivateKey
|
|
ServerCert *x509.Certificate
|
|
ServerCertificateChain string
|
|
ServerCAPool *x509.CertPool
|
|
}
|
|
|
|
type KmipServer struct {
|
|
certs CertificateSet
|
|
server Server
|
|
|
|
listenCh chan error
|
|
}
|
|
|
|
func (s *KmipServer) loadCertificates() error {
|
|
client := resty.New()
|
|
|
|
payload := KmipServerRegistrationAPIRequest{
|
|
HostnamesOrIps: s.server.HostnamesOrIps,
|
|
CommonName: s.server.ServerName,
|
|
TTL: s.server.CertificateTTL,
|
|
}
|
|
|
|
apiResp, err := client.R().
|
|
SetHeader("Authorization", fmt.Sprintf("Bearer %s", s.server.KmsAuth.GetAccessToken())).
|
|
SetHeader("Content-Type", "application/json").
|
|
SetBody(payload).
|
|
Post(fmt.Sprintf("%s/v1/kmip/server-registration", s.server.KmsBaseAPIURL))
|
|
|
|
if err != nil {
|
|
s.server.Log.Printf("Error: %+v\n", err)
|
|
return kmsErrors.NewRequestError("ServerRegistration", err)
|
|
}
|
|
|
|
if apiResp.IsError() {
|
|
return kmsErrors.NewAPIErrorWithResponse("ServerRegistration", apiResp)
|
|
}
|
|
|
|
var result KmipServerRegistrationAPIResponse
|
|
if err := json.Unmarshal(apiResp.Body(), &result); err != nil {
|
|
return errors.Wrap(err, "failed to decode response")
|
|
}
|
|
|
|
block, _ := pem.Decode([]byte(result.PrivateKey))
|
|
if block == nil || block.Type != "PRIVATE KEY" {
|
|
return errors.New("failed to decode PEM block containing server private key")
|
|
}
|
|
|
|
key, err := x509.ParsePKCS8PrivateKey(block.Bytes)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "error parsing server private key")
|
|
}
|
|
|
|
var ok bool
|
|
switch key := key.(type) {
|
|
case *ecdsa.PrivateKey:
|
|
s.certs.ServerKey = key
|
|
ok = true
|
|
case *rsa.PrivateKey:
|
|
s.certs.ServerKey = key
|
|
ok = true
|
|
default:
|
|
ok = false
|
|
}
|
|
|
|
if !ok {
|
|
return errors.New("server private key is not of a supported type (ECDSA or RSA)")
|
|
}
|
|
|
|
block, _ = pem.Decode([]byte(result.Certificate))
|
|
if block == nil {
|
|
return errors.New("failed to decode PEM block containing server certificate")
|
|
}
|
|
|
|
s.certs.ServerCert, err = x509.ParseCertificate(block.Bytes)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "error parsing server cert")
|
|
}
|
|
|
|
s.certs.ServerCertificateChain = result.CertificateChain
|
|
|
|
s.certs.ServerCAPool = x509.NewCertPool()
|
|
clientChainPemBytes := []byte(result.ClientCertificateChain)
|
|
for {
|
|
block, rest := pem.Decode(clientChainPemBytes)
|
|
if block == nil {
|
|
break
|
|
}
|
|
|
|
cert, err := x509.ParseCertificate(block.Bytes)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "error parsing certificate in client chain")
|
|
}
|
|
|
|
s.certs.ServerCAPool.AddCert(cert)
|
|
clientChainPemBytes = rest
|
|
}
|
|
|
|
log.Println("Certificates loaded successfully")
|
|
return nil
|
|
}
|
|
|
|
type ServerConfig struct {
|
|
Addr string
|
|
KmsBaseAPIURL string
|
|
IdentityClientId string
|
|
IdentityClientSecret string
|
|
|
|
ServerName string
|
|
CertificateTTL string
|
|
HostnamesOrIps string
|
|
}
|
|
|
|
func StartServer(config ServerConfig) {
|
|
kmip := &KmipServer{}
|
|
|
|
err := ValidateHostnamesOrIPs(config.HostnamesOrIps)
|
|
if err != nil {
|
|
log.Fatalf("Validation for field HostnamesOrIps failed. %v", err)
|
|
}
|
|
|
|
err = ValidateDuration(config.CertificateTTL)
|
|
if err != nil {
|
|
log.Fatalf("Validation for field CertificateTTL failed. %v", err)
|
|
}
|
|
|
|
kmip.server.Addr = config.Addr
|
|
kmip.server.KmsBaseAPIURL = config.KmsBaseAPIURL
|
|
kmip.server.ServerName = config.ServerName
|
|
kmip.server.CertificateTTL = config.CertificateTTL
|
|
kmip.server.HostnamesOrIps = config.HostnamesOrIps
|
|
|
|
kmip.server.Log = log.New(os.Stderr, "[kmip] ", log.LstdFlags)
|
|
|
|
kmsClient := kms.NewClient(context.Background(), kms.Config{
|
|
SiteUrl: strings.TrimSuffix(kmip.server.KmsBaseAPIURL, "/api"),
|
|
AutoTokenRefresh: true,
|
|
})
|
|
|
|
kmip.server.KmsAuth = kmsClient.Auth()
|
|
machineIdentityClientId := config.IdentityClientId
|
|
machineIdentityClientSecret := config.IdentityClientSecret
|
|
|
|
// TODO: add support for other auth methods
|
|
_, err = kmip.server.KmsAuth.UniversalAuthLogin(machineIdentityClientId, machineIdentityClientSecret)
|
|
if err != nil {
|
|
log.Fatalf("error authenticating with Hanzo KMS. %v", err)
|
|
return
|
|
}
|
|
|
|
err = kmip.loadCertificates()
|
|
if err != nil {
|
|
log.Fatalf("error loading certificates from Hanzo KMS. %v", err)
|
|
return
|
|
}
|
|
|
|
kmip.server.SessionAuthHandler = func(conn net.Conn) (sessionAuth SessionAuth, err error) {
|
|
if tlsConn, ok := conn.(*tls.Conn); ok {
|
|
state := tlsConn.ConnectionState()
|
|
if len(state.PeerCertificates) > 0 {
|
|
clientCert := state.PeerCertificates[0]
|
|
return SessionAuth{
|
|
ClientCertificateSerialNumber: clientCert.SerialNumber.String(),
|
|
ClientId: clientCert.Subject.CommonName,
|
|
ProjectId: clientCert.Subject.OrganizationalUnit[0],
|
|
}, nil
|
|
} else {
|
|
return SessionAuth{}, errors.New("no client certificate provided")
|
|
}
|
|
}
|
|
return SessionAuth{}, errors.New("connection is not a TLS connection")
|
|
}
|
|
|
|
kmip.server.TLSConfig = &tls.Config{}
|
|
kmip.server.TLSConfig.MinVersion = tls.VersionTLS12
|
|
kmip.server.TLSConfig.ClientAuth = tls.RequireAndVerifyClientCert
|
|
kmip.server.TLSConfig.ClientCAs = kmip.certs.ServerCAPool
|
|
|
|
kmip.server.CertificatePrivateKey = kmip.certs.ServerKey
|
|
kmip.server.CertificateSerialNumber = kmip.certs.ServerCert.SerialNumber.Text(16)
|
|
|
|
// Parse the server certificate chain, excluding root certificates
|
|
var certChain [][]byte
|
|
certChain = append(certChain, kmip.certs.ServerCert.Raw)
|
|
|
|
if kmip.certs.ServerCertificateChain != "" {
|
|
chainPemBytes := []byte(kmip.certs.ServerCertificateChain)
|
|
for {
|
|
block, rest := pem.Decode(chainPemBytes)
|
|
if block == nil {
|
|
break
|
|
}
|
|
|
|
cert, err := x509.ParseCertificate(block.Bytes)
|
|
if err != nil {
|
|
kmip.server.Log.Printf("Warning: error parsing certificate in server chain: %v", err)
|
|
break
|
|
}
|
|
|
|
if cert.Issuer.String() != cert.Subject.String() {
|
|
certChain = append(certChain, cert.Raw)
|
|
}
|
|
|
|
chainPemBytes = rest
|
|
}
|
|
}
|
|
|
|
kmip.server.TLSConfig.Certificates = []tls.Certificate{
|
|
{
|
|
Certificate: certChain,
|
|
PrivateKey: kmip.certs.ServerKey,
|
|
},
|
|
}
|
|
|
|
kmip.server.ReadTimeout = time.Second * 10
|
|
kmip.server.WriteTimeout = time.Second * 10
|
|
|
|
kmip.listenCh = make(chan error, 1)
|
|
initializedCh := make(chan struct{})
|
|
|
|
go func() {
|
|
kmip.listenCh <- kmip.server.ListenAndServe(initializedCh)
|
|
}()
|
|
|
|
<-initializedCh
|
|
kmip.server.Log.Println("Server initialized")
|
|
<-kmip.listenCh
|
|
kmip.server.Log.Println("Server closed")
|
|
}
|