mirror of
https://github.com/kittywitch/nixfiles.git
synced 2026-02-09 04:19:19 -08:00
ops: refactor + CA stuff
This commit is contained in:
parent
5da80d3c52
commit
671d858731
11 changed files with 426 additions and 171 deletions
5
go.mod
5
go.mod
|
|
@ -5,8 +5,11 @@ go 1.18
|
||||||
require (
|
require (
|
||||||
github.com/creasty/defaults v1.6.0
|
github.com/creasty/defaults v1.6.0
|
||||||
github.com/pulumi/pulumi-cloudflare/sdk/v4 v4.15.0
|
github.com/pulumi/pulumi-cloudflare/sdk/v4 v4.15.0
|
||||||
|
github.com/pulumi/pulumi-command/sdk v0.7.0
|
||||||
github.com/pulumi/pulumi-tailscale/sdk v0.11.0
|
github.com/pulumi/pulumi-tailscale/sdk v0.11.0
|
||||||
|
github.com/pulumi/pulumi-tls/sdk/v4 v4.6.1
|
||||||
github.com/pulumi/pulumi/sdk/v3 v3.52.1
|
github.com/pulumi/pulumi/sdk/v3 v3.52.1
|
||||||
|
golang.org/x/crypto v0.0.0-20220824171710-5757bc0c5503
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -45,6 +48,7 @@ require (
|
||||||
github.com/sabhiram/go-gitignore v0.0.0-20180611051255-d3107576ba94 // indirect
|
github.com/sabhiram/go-gitignore v0.0.0-20180611051255-d3107576ba94 // indirect
|
||||||
github.com/santhosh-tekuri/jsonschema/v5 v5.0.0 // indirect
|
github.com/santhosh-tekuri/jsonschema/v5 v5.0.0 // indirect
|
||||||
github.com/sergi/go-diff v1.1.0 // indirect
|
github.com/sergi/go-diff v1.1.0 // indirect
|
||||||
|
github.com/spf13/cast v1.3.1 // indirect
|
||||||
github.com/spf13/cobra v1.4.0 // indirect
|
github.com/spf13/cobra v1.4.0 // indirect
|
||||||
github.com/spf13/pflag v1.0.5 // indirect
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
github.com/texttheater/golang-levenshtein v0.0.0-20191208221605-eb6844b05fc6 // indirect
|
github.com/texttheater/golang-levenshtein v0.0.0-20191208221605-eb6844b05fc6 // indirect
|
||||||
|
|
@ -53,7 +57,6 @@ require (
|
||||||
github.com/uber/jaeger-lib v2.2.0+incompatible // indirect
|
github.com/uber/jaeger-lib v2.2.0+incompatible // indirect
|
||||||
github.com/xanzy/ssh-agent v0.3.2 // indirect
|
github.com/xanzy/ssh-agent v0.3.2 // indirect
|
||||||
go.uber.org/atomic v1.6.0 // indirect
|
go.uber.org/atomic v1.6.0 // indirect
|
||||||
golang.org/x/crypto v0.0.0-20220824171710-5757bc0c5503 // indirect
|
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect
|
||||||
golang.org/x/sys v0.0.0-20220823224334-20c2bfdbfe24 // indirect
|
golang.org/x/sys v0.0.0-20220823224334-20c2bfdbfe24 // indirect
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
|
||||||
|
|
|
||||||
5
go.sum
5
go.sum
|
|
@ -131,8 +131,12 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
|
||||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
github.com/pulumi/pulumi-cloudflare/sdk/v4 v4.15.0 h1:qfebMdTdCfeaSAW/gGeLlSkHI+K7v44AcIj5FEwzwMU=
|
github.com/pulumi/pulumi-cloudflare/sdk/v4 v4.15.0 h1:qfebMdTdCfeaSAW/gGeLlSkHI+K7v44AcIj5FEwzwMU=
|
||||||
github.com/pulumi/pulumi-cloudflare/sdk/v4 v4.15.0/go.mod h1:V1v0FFcbK5rzT62Qgg6eMHBgHeo3mBkSuAjtHTCFWyA=
|
github.com/pulumi/pulumi-cloudflare/sdk/v4 v4.15.0/go.mod h1:V1v0FFcbK5rzT62Qgg6eMHBgHeo3mBkSuAjtHTCFWyA=
|
||||||
|
github.com/pulumi/pulumi-command/sdk v0.7.0 h1:gBxTtg6lY29wbu/XZHsLo6Syoc2yieDmTrSAuxLBRb4=
|
||||||
|
github.com/pulumi/pulumi-command/sdk v0.7.0/go.mod h1:YX0Ri1ezMr4mk8j4S/S1gjJpidt63mMG2C+VXDoTlpU=
|
||||||
github.com/pulumi/pulumi-tailscale/sdk v0.11.0 h1:OmWHFLlSaMOc31jkWGvyaDa+HuW7biJ6R4L+/l/gwTQ=
|
github.com/pulumi/pulumi-tailscale/sdk v0.11.0 h1:OmWHFLlSaMOc31jkWGvyaDa+HuW7biJ6R4L+/l/gwTQ=
|
||||||
github.com/pulumi/pulumi-tailscale/sdk v0.11.0/go.mod h1:H1FaTimvK+hdaHa0rcfw2+DPYQnewBnI5eCcw9DEDXU=
|
github.com/pulumi/pulumi-tailscale/sdk v0.11.0/go.mod h1:H1FaTimvK+hdaHa0rcfw2+DPYQnewBnI5eCcw9DEDXU=
|
||||||
|
github.com/pulumi/pulumi-tls/sdk/v4 v4.6.1 h1:/6DaTsUlz9fuNuJYVMRDwgdTSlp5U2wZ5IXD83iBx8c=
|
||||||
|
github.com/pulumi/pulumi-tls/sdk/v4 v4.6.1/go.mod h1:fG7bnaoul00zCW3rrpS/dwWfko4sZxFVhP+3ml1Jqj0=
|
||||||
github.com/pulumi/pulumi/sdk/v3 v3.30.0/go.mod h1:hGo/+AL1L4sPL9Ukd/i5bNFM3WHs3dHcA+GKEW7M3RA=
|
github.com/pulumi/pulumi/sdk/v3 v3.30.0/go.mod h1:hGo/+AL1L4sPL9Ukd/i5bNFM3WHs3dHcA+GKEW7M3RA=
|
||||||
github.com/pulumi/pulumi/sdk/v3 v3.52.1 h1:Q61zRZvph+RLgWlPagsyuWXtOcF1IcNlqWNvV1LE+vQ=
|
github.com/pulumi/pulumi/sdk/v3 v3.52.1 h1:Q61zRZvph+RLgWlPagsyuWXtOcF1IcNlqWNvV1LE+vQ=
|
||||||
github.com/pulumi/pulumi/sdk/v3 v3.52.1/go.mod h1:IYcBrkAwKEGRVq7R1ne3XJKB5bcux5eL3M/zqco7d6Y=
|
github.com/pulumi/pulumi/sdk/v3 v3.52.1/go.mod h1:IYcBrkAwKEGRVq7R1ne3XJKB5bcux5eL3M/zqco7d6Y=
|
||||||
|
|
@ -150,6 +154,7 @@ github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
|
||||||
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||||
|
github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
|
||||||
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||||
github.com/spf13/cobra v1.4.0 h1:y+wJpx64xcgO1V+RcnwW0LEHxTKRi2ZDPSBjWnrg88Q=
|
github.com/spf13/cobra v1.4.0 h1:y+wJpx64xcgO1V+RcnwW0LEHxTKRi2ZDPSBjWnrg88Q=
|
||||||
github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g=
|
github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g=
|
||||||
|
|
|
||||||
41
iac/ca.go
Normal file
41
iac/ca.go
Normal 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
5
iac/config.go
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
package iac
|
||||||
|
|
||||||
|
type KatConfig struct {
|
||||||
|
Zones map[string]Zone `yaml:"zones"`
|
||||||
|
}
|
||||||
45
iac/dns.go
Normal file
45
iac/dns.go
Normal 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
21
iac/helpers.go
Normal 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
93
iac/record.go
Normal 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
104
iac/tailscale.go
Normal 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
54
iac/tls.go
Normal 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
24
iac/zone.go
Normal 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
|
||||||
|
}
|
||||||
|
|
||||||
200
main.go
200
main.go
|
|
@ -3,196 +3,56 @@ package main
|
||||||
import (
|
import (
|
||||||
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
|
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
|
||||||
tailscale "github.com/pulumi/pulumi-tailscale/sdk/go/tailscale"
|
tailscale "github.com/pulumi/pulumi-tailscale/sdk/go/tailscale"
|
||||||
cloudflare "github.com/pulumi/pulumi-cloudflare/sdk/v4/go/cloudflare"
|
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
"github.com/creasty/defaults"
|
|
||||||
"log"
|
"log"
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
iac "kittywitch/iac"
|
||||||
"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)
|
|
||||||
}
|
|
||||||
|
|
||||||
type Zone struct {
|
|
||||||
Zone string `yaml:"name"`
|
|
||||||
Records []DNSRecord `yaml:"records"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Config struct {
|
|
||||||
Zones map[string]Zone `yaml:"zones"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (z *Zone) handle(ctx *pulumi.Context, name string) (*cloudflare.Zone, error) {
|
|
||||||
return cloudflare.NewZone(ctx, name, &cloudflare.ZoneArgs{
|
|
||||||
Zone: pulumi.String(z.Zone),
|
|
||||||
Plan: pulumi.String("free"),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
config := Config{}
|
katConfig := iac.KatConfig{}
|
||||||
|
|
||||||
|
configFile, err := os.ReadFile("config.yaml")
|
||||||
|
|
||||||
configfile, err := os.ReadFile("config.yaml")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
if err := yaml.Unmarshal(configfile, &config); err != nil {
|
|
||||||
|
if err := yaml.Unmarshal(configFile, &katConfig); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
pulumi.Run(func(ctx *pulumi.Context) error {
|
pulumi.Run(func(ctx *pulumi.Context) error {
|
||||||
ctx.Log.Info(fmt.Sprintf("%v\n", config), nil)
|
|
||||||
tailnet, err := tailscale.GetDevices(ctx, &tailscale.GetDevicesArgs{}, nil)
|
tailnet, err := tailscale.GetDevices(ctx, &tailscale.GetDevicesArgs{}, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
zones := make(map[string]*cloudflare.Zone)
|
|
||||||
dnssec := make(map[string]*cloudflare.ZoneDnssec)
|
// zones, dnssec, records
|
||||||
records := make(map[string][]*cloudflare.Record)
|
zones, _, records, err := iac.HandleDNS(ctx, katConfig)
|
||||||
for name, zone := range config.Zones {
|
|
||||||
ctx.Log.Info(name, nil)
|
if err != nil {
|
||||||
zones[name], err = zone.handle(ctx, name)
|
log.Fatal(err)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
dnssec[name], err = cloudflare.NewZoneDnssec(ctx, fmt.Sprintf("%s-dnssec", name), &cloudflare.ZoneDnssecArgs{
|
|
||||||
ZoneId: zones[name].ID(),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, record := range zone.Records {
|
|
||||||
_, exists := records[name]
|
|
||||||
if exists {
|
|
||||||
record_, err := record.handle(ctx, name, zones[name])
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
records[name] = append(records[name], record_)
|
|
||||||
} else {
|
|
||||||
record_, err := record.handle(ctx, name, zones[name])
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
records[name] = []*cloudflare.Record{record_}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, device := range tailnet.Devices {
|
records, err = iac.HandleTSRecords(ctx, tailnet, zones, records)
|
||||||
if device.User != "kat@inskip.me" {
|
|
||||||
continue
|
if err != nil {
|
||||||
}
|
log.Fatal(err)
|
||||||
device_name := strings.Split(device.Name, ".")[0]
|
|
||||||
ipv4 := DNSRecord{
|
|
||||||
Name: device_name,
|
|
||||||
Kind: A,
|
|
||||||
Value: device.Addresses[0],
|
|
||||||
Ttl: 3600,
|
|
||||||
}
|
|
||||||
recv4, err := ipv4.handle(ctx, "inskip", zones["inskip"])
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
ipv6 := DNSRecord{
|
|
||||||
Name: device_name,
|
|
||||||
Kind: AAAA,
|
|
||||||
Value: device.Addresses[1],
|
|
||||||
Ttl: 3600,
|
|
||||||
}
|
|
||||||
recv6, err := ipv6.handle(ctx, "inskip", zones["inskip"])
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
records["inskip"] = append(records["inskip"], recv4, recv6)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
ca_key, ca_cert, err := iac.GenerateTLSCA(ctx)
|
||||||
log.Fatal(err)
|
|
||||||
}
|
if err != nil {
|
||||||
return nil
|
log.Fatal(err)
|
||||||
})
|
}
|
||||||
|
|
||||||
|
// keys, crs, certs
|
||||||
|
_, _, _, err = iac.HandleTSHostCerts(ctx, tailnet, ca_key, ca_cert)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue