ops: refactor + CA stuff

This commit is contained in:
Kat Inskip 2023-01-27 15:21:35 -08:00
parent 5da80d3c52
commit 671d858731
Signed by: kat
GPG key ID: 465E64DECEA8CF0F
11 changed files with 426 additions and 171 deletions

41
iac/ca.go Normal file
View file

@ -0,0 +1,41 @@
package iac
import(
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
tls "github.com/pulumi/pulumi-tls/sdk/v4/go/tls"
"log"
)
func GenerateTLSCA(ctx *pulumi.Context) (key *tls.PrivateKey, cert *tls.SelfSignedCert, err error) {
key, err = tls.NewPrivateKey(ctx, "kat-root-ca-key", &tls.PrivateKeyArgs{
Algorithm: pulumi.String("RSA"),
RsaBits: pulumi.Int(4096),
})
if err != nil {
log.Fatal(err)
}
cert, err = tls.NewSelfSignedCert(ctx, "kat-root-ca-pem-cert", &tls.SelfSignedCertArgs{
PrivateKeyPem: key.PrivateKeyPem,
AllowedUses: goStringArrayToPulumiStringArray([]string{"digital_signature",
"cert_signing",
"crl_signing"}),
IsCaCertificate: pulumi.Bool(true),
ValidityPeriodHours: pulumi.Int(2562047),
Subject: &tls.SelfSignedCertSubjectArgs{
CommonName: pulumi.String("inskip.me"),
Organization: pulumi.String("Kat Inskip"),
},
})
if err != nil {
log.Fatal(err)
}
ctx.Export("tls_ca_pem_key", key.PrivateKeyPem)
ctx.Export("tls_ca_os_key", key.PrivateKeyOpenssh)
ctx.Export("tls_ca_cert", cert.CertPem)
return key, cert, err
}

5
iac/config.go Normal file
View file

@ -0,0 +1,5 @@
package iac
type KatConfig struct {
Zones map[string]Zone `yaml:"zones"`
}

45
iac/dns.go Normal file
View file

@ -0,0 +1,45 @@
package iac
import(
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
cloudflare "github.com/pulumi/pulumi-cloudflare/sdk/v4/go/cloudflare"
"fmt"
)
func HandleDNS(ctx *pulumi.Context, config KatConfig) (zones map[string]*cloudflare.Zone, dnssec map[string]*cloudflare.ZoneDnssec, records map[string][]*cloudflare.Record, err error) {
zones = make(map[string]*cloudflare.Zone)
dnssec = make(map[string]*cloudflare.ZoneDnssec)
records = make(map[string][]*cloudflare.Record)
for name, zone := range config.Zones {
ctx.Log.Info(fmt.Sprintf("Handling zone %s", name), nil)
zones[name], err = zone.handle(ctx, name)
if err != nil {
return nil, nil, nil, err
}
dnssec[name], err = cloudflare.NewZoneDnssec(ctx, fmt.Sprintf("%s-dnssec", name), &cloudflare.ZoneDnssecArgs{
ZoneId: zones[name].ID(),
})
if err != nil {
return nil, nil, nil, err
}
for _, record := range zone.Records {
_, exists := records[name]
if exists {
record_, err := record.handle(ctx, name, zones[name])
if err != nil {
return nil, nil, nil, err
}
records[name] = append(records[name], record_)
} else {
record_, err := record.handle(ctx, name, zones[name])
if err != nil {
return nil, nil, nil, err
}
records[name] = []*cloudflare.Record{record_}
}
}
}
return zones, dnssec, records, err
}

21
iac/helpers.go Normal file
View file

@ -0,0 +1,21 @@
package iac
import (
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func goStringArrayToPulumiStringArray(ss []string) pulumi.StringArray {
var elems []pulumi.StringInput
for _, s := range ss {
elems = append(elems, pulumi.String(s))
}
return pulumi.StringArray(elems)
}
func goMapToPulumiMap(m map[string]string) pulumi.StringMap {
ret := make(pulumi.StringMap)
for k, v := range m {
ret[k] = pulumi.String(v)
}
return ret
}

93
iac/record.go Normal file
View file

@ -0,0 +1,93 @@
package iac
import (
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
cloudflare "github.com/pulumi/pulumi-cloudflare/sdk/v4/go/cloudflare"
"github.com/creasty/defaults"
"fmt"
"strings"
"crypto/md5"
"encoding/hex"
)
type DNSRecordType string
const (
A DNSRecordType = "a"
AAAA = "aaaa"
MX = "mx"
TXT = "txt"
CAA = "caa"
)
type DNSRecord struct {
Name string `default:"@" yaml:"name"`
Kind DNSRecordType `yaml:"kind"`
Value string `yaml:"value,omitempty"`
Priority int `yaml:"priority,omitempty"`
Flags string `yaml:"flags,omitempty"`
Tag string `yaml:"tag,omitempty"`
Ttl int `default:"3600" yaml:"ttl,omitempty"`
}
func (r *DNSRecord) UnmarshalYAML(unmarshal func(interface{}) error) error {
defaults.Set(r)
type plain DNSRecord
if err := unmarshal((*plain)(r)); err != nil {
return err
}
return nil
}
func (r *DNSRecord) getZone(Zone *cloudflare.Zone) (pulumi.StringOutput) {
return Zone.ID().ToStringOutput()
}
func (r *DNSRecord) getName(ZoneName string, Zone *cloudflare.Zone) (string) {
base := fmt.Sprintf("%s-%s-%s", ZoneName, r.Kind, r.Name)
hash := md5.Sum([]byte(r.Value))
hashString := hex.EncodeToString(hash[:])[:5]
suffix := ""
switch r.Kind {
case MX:
suffix = fmt.Sprintf("-%d-%s", r.Priority, hashString)
case CAA:
suffix = fmt.Sprintf("%s-%s", r.Flags, r.Tag)
case A, AAAA, TXT:
suffix = fmt.Sprintf("-%s", hashString)
}
built := base + suffix
return built
}
func (r *DNSRecord) handle(ctx *pulumi.Context, ZoneName string, zone *cloudflare.Zone) (*cloudflare.Record, error) {
var recordArgs *cloudflare.RecordArgs
switch r.Kind {
case CAA:
recordArgs = &cloudflare.RecordArgs{
ZoneId: r.getZone(zone),
Name: pulumi.String(r.Name),
Type: pulumi.String(strings.ToUpper(string(r.Kind))),
Ttl: pulumi.Int(r.Ttl),
Data: &cloudflare.RecordDataArgs{
Flags: pulumi.String(r.Flags),
Tag: pulumi.String(r.Tag),
Value: pulumi.String(r.Value),
},
}
default:
recordArgs = &cloudflare.RecordArgs{
ZoneId: r.getZone(zone),
Name: pulumi.String(r.Name),
Type: pulumi.String(strings.ToUpper(string(r.Kind))),
Ttl: pulumi.Int(r.Ttl),
Priority: pulumi.Int(r.Priority),
Value: pulumi.String(r.Value),
}
}
return cloudflare.NewRecord(ctx, r.getName(ZoneName, zone), recordArgs)
}

104
iac/tailscale.go Normal file
View file

@ -0,0 +1,104 @@
package iac
import (
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
tailscale "github.com/pulumi/pulumi-tailscale/sdk/go/tailscale"
cloudflare "github.com/pulumi/pulumi-cloudflare/sdk/v4/go/cloudflare"
tls "github.com/pulumi/pulumi-tls/sdk/v4/go/tls"
"strings"
"net"
"fmt"
)
func MakeRecord(ctx *pulumi.Context, zones map[string]*cloudflare.Zone, name string, address string) (record *cloudflare.Record, err error) {
ip := net.ParseIP(address)
kind := A;
if ip.To4() != nil {
kind = AAAA;
}
record_ := DNSRecord{
Name: name,
Kind: kind,
Value: ip.String(),
Ttl: 3600,
}
record, err = record_.handle(ctx, "inskip", zones["inskip"])
if err != nil {
return nil, err
}
return record, err
}
func HandleTSRecord(ctx *pulumi.Context, zones map[string]*cloudflare.Zone, device tailscale.GetDevicesDevice) (records []*cloudflare.Record, err error) {
if device.User != "kat@inskip.me" {
return []*cloudflare.Record{}, nil
}
name := strings.Split(device.Name, ".")[0]
for _, address := range device.Addresses {
record, err := MakeRecord(ctx, zones, name, address)
if err != nil {
return nil, err
}
records = append(records, record)
}
return records, err
}
func HandleTSRecords(ctx *pulumi.Context,
tailnet *tailscale.GetDevicesResult,
zones map[string]*cloudflare.Zone,
records map[string][]*cloudflare.Record,
) (records_ map[string][]*cloudflare.Record, err error) {
for _, device := range tailnet.Devices {
record, err := HandleTSRecord(ctx, zones, device)
if err != nil {
return nil, err
}
records["inskip"] = append(records["inskip"], record...)
}
records_ = records
return records_, err
}
func HandleTSHostCert(ctx *pulumi.Context,
device tailscale.GetDevicesDevice,
ca_key *tls.PrivateKey,
ca_cert *tls.SelfSignedCert) (key *tls.PrivateKey,
cr *tls.CertRequest,
cert *tls.LocallySignedCert,
err error) {
name := strings.Split(device.Name, ".")[0]
key, cr, cert, err = generateKeyPair(
ctx,
fmt.Sprintf("ts-%s-host", name),
ca_key,
ca_cert,
device.Addresses,
[]string{fmt.Sprintf("%s.inskip.me", name)},
)
if err != nil {
return nil, nil, nil, err
}
return key, cr, cert, err
}
func HandleTSHostCerts(ctx *pulumi.Context,
tailnet *tailscale.GetDevicesResult,
ca_key *tls.PrivateKey,
ca_cert *tls.SelfSignedCert) (keys map[string]*tls.PrivateKey,
crs map[string]*tls.CertRequest,
certs map[string]*tls.LocallySignedCert,
err error) {
keys = make(map[string]*tls.PrivateKey)
crs = make(map[string]*tls.CertRequest)
certs = make(map[string]*tls.LocallySignedCert)
for _, device := range tailnet.Devices {
name := strings.Split(device.Name, ".")[0]
keys[name], crs[name], certs[name], err = HandleTSHostCert(ctx, device, ca_key, ca_cert)
if err != nil {
return nil, nil, nil, err
}
}
return keys, crs, certs, err
}

54
iac/tls.go Normal file
View file

@ -0,0 +1,54 @@
package iac
import (
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
tls "github.com/pulumi/pulumi-tls/sdk/v4/go/tls"
"fmt"
)
func generateKeyPair(ctx *pulumi.Context,
purpose string,
ca_key *tls.PrivateKey,
ca_cert *tls.SelfSignedCert,
dns_names []string,
ip_addresses []string) (key *tls.PrivateKey,
cr *tls.CertRequest,
cert *tls.LocallySignedCert,
err error) {
key, err = tls.NewPrivateKey(ctx, fmt.Sprintf("%s-key", purpose), &tls.PrivateKeyArgs{
Algorithm: pulumi.String("RSA"),
RsaBits: pulumi.Int(4096),
})
if err != nil {
return nil, nil, nil, err
}
cr, err = tls.NewCertRequest(ctx, fmt.Sprintf("%s-cr", purpose), &tls.CertRequestArgs{
PrivateKeyPem: key.PrivateKeyPem,
DnsNames: goStringArrayToPulumiStringArray(dns_names),
IpAddresses: goStringArrayToPulumiStringArray(ip_addresses),
Subject: &tls.CertRequestSubjectArgs{
CommonName: pulumi.String("inskip.me"),
Organization: pulumi.String("Kat Inskip"),
},
})
if err != nil {
return nil, nil, nil, err
}
cert, err = tls.NewLocallySignedCert(ctx, fmt.Sprintf("%s-cert", purpose), &tls.LocallySignedCertArgs{
AllowedUses: goStringArrayToPulumiStringArray([]string{"digital_signature",
"digital_signature",
"key_encipherment",
"key_agreement",
"email_protection",
}),
CaPrivateKeyPem: ca_key.PrivateKeyPem,
CaCertPem: ca_cert.CertPem,
CertRequestPem: cr.CertRequestPem,
ValidityPeriodHours: pulumi.Int(1440),
EarlyRenewalHours: pulumi.Int(168),
})
if err != nil {
return nil, nil, nil, err
}
return key, cr, cert, err
}

24
iac/zone.go Normal file
View file

@ -0,0 +1,24 @@
package iac
import (
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
cloudflare "github.com/pulumi/pulumi-cloudflare/sdk/v4/go/cloudflare"
)
type Zone struct {
Zone string `yaml:"name"`
Records []DNSRecord `yaml:"records"`
}
func (z *Zone) handle(ctx *pulumi.Context, name string) (zone *cloudflare.Zone, err error) {
zone, err = cloudflare.NewZone(ctx, name, &cloudflare.ZoneArgs{
AccountId: pulumi.ID("0467b993b65d8fd4a53fe24ed2fbb2a1"),
Zone: pulumi.String(z.Zone),
Plan: pulumi.String("free"),
})
if err != nil {
return nil, err
}
return zone, err
}