mirror of
https://gerrit.hackerspace.pl/hscloud
synced 2025-01-24 17:03:56 +00:00
radex
20288e8aa9
- remove defaults for -kubernetes_host and -prodvider_cn - pass cluster fqdn to KubernetesKeys.Cluster - hardcode prodvider.hswaw.net as one of prodvider cert's DNSNames to allow graceful transition to prodvider.k0.hswaw.net - add optional -crdb_cluster flag BONUS CONTENT: - use consistent credential duration for all certs + allow configuration via -credential_duration - fixes broken prodviding if username isn't all lowercase Change-Id: Ia801a16d7245d746e72f199a0900100ffc614dcf Reviewed-on: https://gerrit.hackerspace.pl/c/hscloud/+/2100 Reviewed-by: q3k <q3k@hackerspace.pl>
194 lines
5.2 KiB
Go
194 lines
5.2 KiB
Go
package main
|
|
|
|
import (
|
|
"crypto/ed25519"
|
|
"crypto/x509"
|
|
"encoding/pem"
|
|
"flag"
|
|
"fmt"
|
|
"math/rand"
|
|
"net"
|
|
"os"
|
|
"time"
|
|
|
|
"github.com/golang/glog"
|
|
"google.golang.org/grpc"
|
|
"k8s.io/client-go/kubernetes"
|
|
|
|
pb "code.hackerspace.pl/hscloud/cluster/prodvider/proto"
|
|
)
|
|
|
|
var (
|
|
flagLDAPServer string
|
|
flagLDAPBindDN string
|
|
flagLDAPGroupSearchBase string
|
|
flagListenAddress string
|
|
flagKubeFqdn string
|
|
flagKubeApiserverAddress string
|
|
|
|
flagCACertificatePath string
|
|
flagCAKeyPath string
|
|
flagKubeCACertificatePath string
|
|
|
|
flagProdviderCN string
|
|
|
|
flagCrdbCluster string
|
|
|
|
flagCredentialDuration time.Duration
|
|
)
|
|
|
|
func init() {
|
|
flag.Set("logtostderr", "true")
|
|
}
|
|
|
|
type prodvider struct {
|
|
k8s *kubernetes.Clientset
|
|
srv *grpc.Server
|
|
|
|
intermediateCAKey ed25519.PrivateKey
|
|
intermediateCACert *x509.Certificate
|
|
kubeCACert *x509.Certificate
|
|
}
|
|
|
|
func loadCert(path string) (*x509.Certificate, error) {
|
|
b, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
block, _ := pem.Decode(b)
|
|
if block == nil {
|
|
return nil, fmt.Errorf("no PEM block found")
|
|
}
|
|
if block.Type != "CERTIFICATE" {
|
|
return nil, fmt.Errorf("unexpected PEM block: %q", block.Type)
|
|
}
|
|
return x509.ParseCertificate(block.Bytes)
|
|
}
|
|
|
|
func loadKey(path string) (ed25519.PrivateKey, error) {
|
|
bytes, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
block, _ := pem.Decode(bytes)
|
|
if block == nil {
|
|
return nil, fmt.Errorf("no PEM block found")
|
|
}
|
|
if block.Type != "PRIVATE KEY" {
|
|
return nil, fmt.Errorf("unexpected PEM block: %q", block.Type)
|
|
}
|
|
key, err := x509.ParsePKCS8PrivateKey(block.Bytes)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if k, ok := key.(ed25519.PrivateKey); ok {
|
|
return k, nil
|
|
}
|
|
return nil, fmt.Errorf("not an ED25519 key")
|
|
}
|
|
|
|
func newProdvider() *prodvider {
|
|
kubeCACert, err := loadCert(flagKubeCACertificatePath)
|
|
if err != nil {
|
|
glog.Exitf("Loading kube CA certificate failed: %v", err)
|
|
}
|
|
intermediateCACert, err := loadCert(flagCACertificatePath)
|
|
if err != nil {
|
|
glog.Exitf("Loading intermediate CAcertificate failed: %v", err)
|
|
}
|
|
intermediateCAKey, err := loadKey(flagCAKeyPath)
|
|
return &prodvider{
|
|
intermediateCAKey: intermediateCAKey,
|
|
intermediateCACert: intermediateCACert,
|
|
kubeCACert: kubeCACert,
|
|
}
|
|
}
|
|
|
|
// Timebomb restarts the prodvider after a deadline, usually 7 days +/- 4 days.
|
|
// This is to ensure we serve with up-to-date certificates and that the service
|
|
// can still come up after restart.
|
|
func timebomb(srv *grpc.Server) {
|
|
deadline := time.Now()
|
|
deadline = deadline.Add(3 * 24 * time.Hour)
|
|
rand.Seed(time.Now().UnixNano())
|
|
jitter := rand.Intn(8 * 24 * 60 * 60)
|
|
deadline = deadline.Add(time.Duration(jitter) * time.Second)
|
|
|
|
glog.Infof("Timebomb deadline set to %v", deadline)
|
|
|
|
t := time.NewTicker(time.Minute)
|
|
for {
|
|
<-t.C
|
|
if time.Now().After(deadline) {
|
|
break
|
|
}
|
|
}
|
|
|
|
// Start killing connections, and wait one minute...
|
|
go srv.GracefulStop()
|
|
<-t.C
|
|
glog.Infof("Timebomb deadline exceeded, restarting.")
|
|
os.Exit(0)
|
|
}
|
|
|
|
func main() {
|
|
flag.StringVar(&flagLDAPServer, "ldap_server", "ldap.hackerspace.pl:636", "Address of LDAP server")
|
|
flag.StringVar(&flagLDAPBindDN, "ldap_bind_dn", "uid=%s,ou=People,dc=hackerspace,dc=pl", "LDAP Bind DN")
|
|
flag.StringVar(&flagLDAPGroupSearchBase, "ldap_group_search_base_dn", "ou=Group,dc=hackerspace,dc=pl", "LDAP Group Search Base DN")
|
|
flag.StringVar(&flagListenAddress, "listen_address", "127.0.0.1:8080", "gRPC listen address")
|
|
flag.StringVar(&flagKubeFqdn, "kube_fqdn", "", "Kubernetes cluster domain name")
|
|
flag.StringVar(&flagKubeApiserverAddress, "kube_apiserver_address", "", "Kubernetes API server address")
|
|
|
|
flag.StringVar(&flagCACertificatePath, "ca_certificate_path", "", "CA certificate path (for signer)")
|
|
flag.StringVar(&flagCAKeyPath, "ca_key_path", "", "CA key path (for signer)")
|
|
flag.StringVar(&flagKubeCACertificatePath, "kube_ca_certificate_path", "", "CA certificate path (for checking kube apiserver)")
|
|
|
|
flag.StringVar(&flagProdviderCN, "prodvider_cn", "", "CN of certificate that prodvider will use (must match domain name)")
|
|
|
|
flag.StringVar(&flagCrdbCluster, "crdb_cluster", "", "CockroachDB cluster name")
|
|
|
|
flag.DurationVar(&flagCredentialDuration, "credential_duration", 12*time.Hour, "Duration of prodvided credentials before expiry")
|
|
flag.Parse()
|
|
|
|
if flagCACertificatePath == "" || flagCAKeyPath == "" || flagKubeCACertificatePath == "" {
|
|
glog.Exitf("CA certificates and key must be provided")
|
|
}
|
|
|
|
if flagProdviderCN == "" {
|
|
glog.Exitf("-prodvider_cn must be provided")
|
|
}
|
|
|
|
if flagKubeFqdn == "" {
|
|
glog.Exitf("-kube_fqdn must be provided")
|
|
}
|
|
|
|
if flagKubeApiserverAddress == "" {
|
|
flagKubeApiserverAddress = net.JoinHostPort(flagKubeFqdn, "4001")
|
|
}
|
|
|
|
p := newProdvider()
|
|
err := p.kubernetesConnect()
|
|
if err != nil {
|
|
glog.Exitf("Could not connect to kubernetes: %v", err)
|
|
}
|
|
creds := p.selfCreds()
|
|
|
|
// Start serving gRPC
|
|
grpcLis, err := net.Listen("tcp", flagListenAddress)
|
|
if err != nil {
|
|
glog.Exitf("Could not listen for gRPC on %q: %v", flagListenAddress, err)
|
|
}
|
|
|
|
glog.Infof("Starting gRPC on %q...", flagListenAddress)
|
|
grpcSrv := grpc.NewServer(creds)
|
|
|
|
pb.RegisterProdviderServer(grpcSrv, p)
|
|
|
|
go timebomb(grpcSrv)
|
|
|
|
err = grpcSrv.Serve(grpcLis)
|
|
if err != nil {
|
|
glog.Exitf("Could not serve gRPC: %v", err)
|
|
}
|
|
}
|