nixfiles/iac/device.go
2023-02-06 10:58:51 -08:00

205 lines
6 KiB
Go

package iac
import (
"crypto/rand"
"fmt"
"github.com/pulumi/pulumi-command/sdk/go/command/remote"
"github.com/pulumi/pulumi-tailscale/sdk/go/tailscale"
"github.com/pulumi/pulumi-tls/sdk/v4/go/tls"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
"golang.org/x/crypto/ssh"
"net"
"strings"
"time"
)
type Tailnet struct {
Devices []Device
Zone *Zone
}
func (t *Tailnet) handle(ctx *pulumi.Context, zone *Zone, CAKey *tls.PrivateKey, CACert *tls.SelfSignedCert) (err error) {
t.Zone = zone
tailnet, err := tailscale.GetDevices(ctx, &tailscale.GetDevicesArgs{}, nil)
if err != nil {
return err
}
for _, device := range tailnet.Devices {
device_ := Device{
Addresses: device.Addresses,
Id: device.Id,
Name: device.Name,
Tags: device.Tags,
User: device.User,
}
err = device_.handle(ctx, zone, CAKey, CACert)
if err != nil {
return err
}
t.Devices = append(t.Devices, device_)
}
return err
}
type Device struct {
Addresses []string
Id string
Name string
Hostname string
Tailskip string
Tags []string
User string
Files []*remote.Command
Context *pulumi.Context
Records []DNSRecord
PrivateKey *tls.PrivateKey
TLSCertRequest *tls.CertRequest
TLSCert *tls.LocallySignedCert
OSHCertificate pulumi.StringOutput
}
func (d *Device) handle(ctx *pulumi.Context, zone *Zone, CAKey *tls.PrivateKey, CACert *tls.SelfSignedCert) (err error) {
d.Context = ctx
d.Records = make([]DNSRecord, 0, 50)
d.Files = make([]*remote.Command, 0, 20)
d.Hostname = strings.Split(d.Name, ".")[0]
d.Tailskip = fmt.Sprintf("%s.inskip.me", d.Hostname)
if d.User != "kat@inskip.me" {
return nil
}
err = d.record(zone)
if err != nil {
return err
}
if d.Hostname != "koishi" && d.Hostname != "tewi" {
return err
}
err = d.handleTLS(CAKey, CACert)
if err != nil {
return err
}
err = d.handleOSH(CAKey)
if err != nil {
return err
}
return err
}
func (d *Device) handleOSH(CAKey *tls.PrivateKey) (err error) {
d.OSHCertificate = CAKey.PrivateKeyOpenssh.ApplyT(func(CAPriv string) pulumi.StringOutput {
OSHCertificate_ := d.PrivateKey.PrivateKeyOpenssh.ApplyT(func(UserPriv string) pulumi.String {
CARSAPriv, err := PrivateKeyOpenSSHToRSAPrivateKey(CAPriv)
if err != nil {
panic(err)
}
signer, err := ssh.NewSignerFromKey(CARSAPriv)
if err != nil {
panic(err)
}
var cert ssh.Certificate
cert.Nonce = make([]byte, 32)
cert.CertType = 2
UserRSAPriv, err := PrivateKeyOpenSSHToRSAPrivateKey(UserPriv)
if err != nil {
panic(err)
}
cert.Key, err = ssh.NewPublicKey(UserRSAPriv.Public())
if err != nil {
panic(err)
}
cert.Serial = 0
cert.KeyId = d.Tailskip
cert.ValidPrincipals = []string{d.Tailskip}
cert.ValidAfter = 60
threeMonths, err := time.ParseDuration("730h")
if err != nil {
panic(err)
}
threeMonthsInSeconds := uint64(threeMonths.Seconds())
cert.ValidBefore = threeMonthsInSeconds
err = cert.SignCert(rand.Reader, signer)
return pulumi.String(string(ssh.MarshalAuthorizedKey(&cert)))
}).(pulumi.StringOutput)
return OSHCertificate_
}).(pulumi.StringOutput)
file, err := CreatePulumiFile(d.Context, fmt.Sprintf("%s-osh-cert", d.Hostname), d.Tailskip, d.OSHCertificate, []pulumi.Resource{d.PrivateKey, CAKey})
d.Files = append(d.Files, file)
return err
}
func (d *Device) record(zone *Zone) (err error) {
for _, address := range d.Addresses {
ip := net.ParseIP(address)
kind := A
if ip.To4() == nil {
kind = AAAA
}
record := DNSRecord{
Name: d.Hostname,
Kind: kind,
Value: ip.String(),
Ttl: 3600,
}
err = record.handle(d.Context, zone)
if err != nil {
return err
}
d.Records = append(d.Records, record)
}
return err
}
func (d *Device) handleTLS(CAKey *tls.PrivateKey, CACert *tls.SelfSignedCert) (err error) {
PrivateKeyDepends := []pulumi.Resource{CAKey, CACert}
d.PrivateKey, err = tls.NewPrivateKey(d.Context, fmt.Sprintf("%s-key", d.Hostname), &tls.PrivateKeyArgs{
Algorithm: pulumi.String("RSA"),
RsaBits: pulumi.Int(4096),
}, pulumi.DependsOn(PrivateKeyDepends))
if err != nil {
return err
}
PrivateKeyDepends = append(PrivateKeyDepends, d.PrivateKey)
file, err := CreatePulumiFile(d.Context, fmt.Sprintf("%s-pem-pk", d.Hostname), d.Tailskip, d.PrivateKey.PrivateKeyPem, PrivateKeyDepends)
if err != nil {
return err
}
d.Files = append(d.Files, file)
file, err = CreatePulumiFile(d.Context, fmt.Sprintf("%s-osh-pk", d.Hostname), d.Tailskip, d.PrivateKey.PrivateKeyOpenssh, PrivateKeyDepends)
if err != nil {
return err
}
d.Files = append(d.Files, file)
TLSCertRequestDepends := []pulumi.Resource{CAKey, CACert, d.PrivateKey}
d.TLSCertRequest, err = tls.NewCertRequest(d.Context, fmt.Sprintf("%s-tls-cr", d.Hostname), &tls.CertRequestArgs{
PrivateKeyPem: d.PrivateKey.PrivateKeyPem,
DnsNames: goStringArrayToPulumiStringArray([]string{d.Hostname}),
IpAddresses: goStringArrayToPulumiStringArray(d.Addresses),
Subject: &tls.CertRequestSubjectArgs{
CommonName: pulumi.String("inskip.me"),
Organization: pulumi.String("Kat Inskip"),
},
}, pulumi.DependsOn(TLSCertRequestDepends))
if err != nil {
return err
}
TLSCertDepends := []pulumi.Resource{CAKey, CACert, d.TLSCertRequest, d.PrivateKey}
d.TLSCert, err = tls.NewLocallySignedCert(d.Context, fmt.Sprintf("%s-tls-cert", d.Hostname), &tls.LocallySignedCertArgs{
AllowedUses: goStringArrayToPulumiStringArray([]string{"digital_signature",
"digital_signature",
"key_encipherment",
"key_agreement",
"email_protection",
}),
CaPrivateKeyPem: CAKey.PrivateKeyPem,
CaCertPem: CACert.CertPem,
CertRequestPem: d.TLSCertRequest.CertRequestPem,
ValidityPeriodHours: pulumi.Int(1440),
EarlyRenewalHours: pulumi.Int(168),
}, pulumi.DependsOn(TLSCertDepends))
file, err = CreatePulumiFile(d.Context, fmt.Sprintf("%s-pem-cert", d.Hostname), d.Tailskip, d.TLSCert.CertPem, TLSCertDepends)
d.Files = append(d.Files, file)
if err != nil {
return err
}
return err
}