mirror of
https://github.com/kittywitch/nixfiles.git
synced 2026-02-09 04:19:19 -08:00
feat: SSH CA
This commit is contained in:
parent
a28e1ce6e2
commit
ccf6a6f704
23 changed files with 678 additions and 431 deletions
8
.idea/.gitignore
generated
vendored
Normal file
8
.idea/.gitignore
generated
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
9
.idea/kittywitch.iml
generated
Normal file
9
.idea/kittywitch.iml
generated
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="Go" enabled="true" />
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/kittywitch.iml" filepath="$PROJECT_DIR$/.idea/kittywitch.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
config:
|
||||
cloudflare:apiToken:
|
||||
secure: AAABACPPwldgPSVHPYdh4lQnYR+baaGsnQRwGw/qwNke8wuFnCTdZIqj6yXu0CCGaYBu0+66LTALNNl9mo9HXs/PMLmoOqLo
|
||||
secure: AAABAFcufTX7tZZf2gcK6hML2tgovDEfcPAJcgjfYkV3GMS4Ilwzuco5p+hCpyj3vCm7cqm3tmdwOLlOFxGqKZGRj+ESXAzv
|
||||
tailscale:apiKey:
|
||||
secure: AAABAGc7s7XJ+voSUNcMmRuVwrUdx3kojn0fdEl6qpUy0WmhgHbk6cEz2/kGSEGhuLGwo3mzOGVTI+NVu6/Xz4PmE9FME++VfE8cz5DFjDrMJ4JdX0DR
|
||||
tailscale:tailnet: inskip.me
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ zones:
|
|||
flags: 0
|
||||
tag: iodef
|
||||
value: mailto:acme@inskip.me
|
||||
- kind: cname
|
||||
value: inskip-root.pages.dev
|
||||
- kind: caa
|
||||
flags: 0
|
||||
tag: issue
|
||||
|
|
|
|||
|
|
@ -1,5 +1,16 @@
|
|||
_: {
|
||||
{ pkgs, ... }: {
|
||||
nix.envVars = {
|
||||
"SSH_AUTH_SOCK" = "/Users/kat/.gnupg/S.gpg-agent.ssh";
|
||||
};
|
||||
|
||||
launchd.daemons.start_nixos_native = {
|
||||
serviceConfig.ProgramArguments = [
|
||||
"/bin/sh" "-c"
|
||||
"/bin/wait4path /nix/store && ${pkgs.writeScript "start_nixos_native" ''
|
||||
/usr/bin/open "utm://start?name=NixOS Native"
|
||||
''}"
|
||||
];
|
||||
serviceConfig.Label = "org.kittywitch.start_nixos_native";
|
||||
serviceConfig.RunAtLoad = true;
|
||||
};
|
||||
}
|
||||
|
|
|
|||
30
flake.lock
generated
30
flake.lock
generated
|
|
@ -24,11 +24,11 @@
|
|||
"arcexprs": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1674878824,
|
||||
"narHash": "sha256-skuyKtydzGFtd2UQB2BZW2bMaUcS+fvHEEz+H4MNQ4g=",
|
||||
"lastModified": 1675107003,
|
||||
"narHash": "sha256-ao5OLwhC7+T3O2ixRrt76gIg9uZR3c17SJL3Wvx0EpQ=",
|
||||
"owner": "arcnmx",
|
||||
"repo": "nixexprs",
|
||||
"rev": "ab1bd348da95d6f33ad28992b5d8c636d2330cc9",
|
||||
"rev": "5b7d1eb5e578da7ed36b2105f80f82f9ad11244d",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
@ -126,11 +126,11 @@
|
|||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1674771519,
|
||||
"narHash": "sha256-U0W3S1nX6yEvLh3Vq70EORbmXecAKXfmEfCfaA4A+I8=",
|
||||
"lastModified": 1675181178,
|
||||
"narHash": "sha256-jymSUUjKoArptU7LJ1i4boysXptnpuETiUTenKgs2fM=",
|
||||
"owner": "nix-community",
|
||||
"repo": "home-manager",
|
||||
"rev": "bb4b25b302dbf0f527f190461b080b5262871756",
|
||||
"rev": "69696fe53940562a047bf2ec675cc1dcd1bc09b3",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
@ -173,11 +173,11 @@
|
|||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1674357402,
|
||||
"narHash": "sha256-oxOCYORXBh0KW4KEwKpzs2LThDdVmEwREV+SsP4CIpg=",
|
||||
"lastModified": 1674962474,
|
||||
"narHash": "sha256-qEXdgW5fnMSdQwP1zQYa0fVtI0f3G1f2qNRjUEherCs=",
|
||||
"owner": "Mic92",
|
||||
"repo": "nix-index-database",
|
||||
"rev": "e9e7c5c62965e7e656febb5ba578d53f751eb41f",
|
||||
"rev": "a385f6192f5471c4cebeeb0d2e966b5ccf123df5",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
@ -188,11 +188,11 @@
|
|||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1674641431,
|
||||
"narHash": "sha256-qfo19qVZBP4qn5M5gXc/h1MDgAtPA5VxJm9s8RUAkVk=",
|
||||
"lastModified": 1675115703,
|
||||
"narHash": "sha256-4zetAPSyY0D77x+Ww9QBe8RHn1akvIvHJ/kgg8kGDbk=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "9b97ad7b4330aacda9b2343396eb3df8a853b4fc",
|
||||
"rev": "2caf4ef5005ecc68141ecb4aac271079f7371c44",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
@ -205,11 +205,11 @@
|
|||
"pypi-deps-db": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1674855014,
|
||||
"narHash": "sha256-SMUq72uWWs+KAlcRB7UXzI1QHNTGiUTpQK2qj1evHXQ=",
|
||||
"lastModified": 1675156620,
|
||||
"narHash": "sha256-lmnsBYJz2Fgm0WFNUgSqskpwa0ffbFOr9YGDZpUXptk=",
|
||||
"owner": "DavHau",
|
||||
"repo": "pypi-deps-db",
|
||||
"rev": "a375715227007ca768d372b2b09bcb76f8f19d78",
|
||||
"rev": "2e236bb32e6e3ef13cd56fc6d9aee8c89059a1ac",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
|
|||
75
iac/ca.go
75
iac/ca.go
|
|
@ -1,40 +1,45 @@
|
|||
package iac
|
||||
|
||||
import(
|
||||
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
|
||||
tls "github.com/pulumi/pulumi-tls/sdk/v4/go/tls"
|
||||
import (
|
||||
tls "github.com/pulumi/pulumi-tls/sdk/v4/go/tls"
|
||||
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
|
||||
)
|
||||
|
||||
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 {
|
||||
return nil, nil, 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 {
|
||||
return nil, nil, 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
|
||||
type CertificateAuthority struct {
|
||||
Key *tls.PrivateKey
|
||||
Cert *tls.SelfSignedCert
|
||||
}
|
||||
|
||||
func (ca *CertificateAuthority) handle(ctx *pulumi.Context) (err error) {
|
||||
ca.Key, err = tls.NewPrivateKey(ctx, "ca-root", &tls.PrivateKeyArgs{
|
||||
Algorithm: pulumi.String("RSA"),
|
||||
RsaBits: pulumi.Int(4096),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ca.Cert, err = tls.NewSelfSignedCert(ctx, "ca-root", &tls.SelfSignedCertArgs{
|
||||
PrivateKeyPem: ca.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 {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx.Export("ca_pem_privkey", ca.Key.PrivateKeyPem)
|
||||
ctx.Export("ca_os_privkey", ca.Key.PrivateKeyOpenssh)
|
||||
ctx.Export("ca_pem_cert", ca.Cert.CertPem)
|
||||
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
package iac
|
||||
|
||||
type KatConfig struct {
|
||||
Zones map[string]Zone `yaml:"zones"`
|
||||
Zones map[string]Zone `yaml:"zones"`
|
||||
}
|
||||
|
|
|
|||
205
iac/device.go
Normal file
205
iac/device.go
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
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" {
|
||||
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
|
||||
}
|
||||
37
iac/dns.go
37
iac/dns.go
|
|
@ -1,37 +0,0 @@
|
|||
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 {
|
||||
record_, err := record.handle(ctx, name, zones[name])
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
record_index := record.getName(name, zones[name])
|
||||
records[record_index] = record_
|
||||
}
|
||||
}
|
||||
|
||||
return zones, dnssec, records, err
|
||||
}
|
||||
54
iac/files.go
54
iac/files.go
|
|
@ -1,43 +1,23 @@
|
|||
package iac
|
||||
|
||||
import (
|
||||
"github.com/pulumi/pulumi-command/sdk/go/command/local"
|
||||
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
|
||||
"github.com/pulumi/pulumi-tls/sdk/v4/go/tls"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"github.com/pulumi/pulumi-command/sdk/go/command/remote"
|
||||
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
|
||||
)
|
||||
|
||||
func createPulumiFile(ctx *pulumi.Context, name string, value pulumi.StringOutput, resource pulumi.Resource) (*local.Command, error) {
|
||||
repo_root := os.Getenv("REPO_ROOT")
|
||||
data_root := path.Join(repo_root, "./data")
|
||||
ctx.Export(name, value)
|
||||
return local.NewCommand(ctx, name, &local.CommandArgs{
|
||||
Create: pulumi.String(fmt.Sprintf("pulumi stack output %s --non-interactive --show-secrets > %s", name, name)),
|
||||
Update: pulumi.String(fmt.Sprintf("pulumi stack output %s --non-interactive --show-secrets > %s", name, name)),
|
||||
Delete: pulumi.String(fmt.Sprintf("rm %s", name)),
|
||||
Dir: pulumi.String(data_root),
|
||||
Environment: goMapToPulumiMap(map[string]string{
|
||||
"PULUMI_SKIP_UPDATE_CHECK": "true",
|
||||
}),
|
||||
}, pulumi.DependsOn([]pulumi.Resource{resource}))
|
||||
}
|
||||
|
||||
func PKITLSFiles(ctx *pulumi.Context, files_ map[string]*local.Command, keys map[string]*tls.PrivateKey, certs map[string]*tls.LocallySignedCert) (files map[string]*local.Command, err error) {
|
||||
for name_, key := range keys {
|
||||
name := fmt.Sprintf("%s-file", name_)
|
||||
files_[name], err = createPulumiFile(ctx, name, key.PrivateKeyPem, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
for name_, cert := range certs {
|
||||
name := fmt.Sprintf("%s-file", name_)
|
||||
files_[name], err = createPulumiFile(ctx, name, cert.CertPem, cert)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return files_, err
|
||||
func CreatePulumiFile(ctx *pulumi.Context, name string, fqdn string, value pulumi.StringOutput, resources []pulumi.Resource) (*remote.Command, error) {
|
||||
environment := goMapToPulumiMap(map[string]string{
|
||||
"PULUMI_SKIP_UPDATE_CHECK": "true",
|
||||
})
|
||||
return remote.NewCommand(ctx, name, &remote.CommandArgs{
|
||||
Connection: &remote.ConnectionArgs{
|
||||
Host: pulumi.String(fqdn),
|
||||
Port: pulumi.Float64Ptr(22),
|
||||
User: pulumi.String("deploy"),
|
||||
AgentSocketPath: pulumi.String("/Users/kat/.gnupg/S.gpg-agent.ssh"),
|
||||
},
|
||||
Create: pulumi.Sprintf("sudo mkdir -p /var/lib/secrets && sudo chown deploy:users -R /var/lib/secrets && cd /var/lib/secrets && echo \"%s\" > \"%s\"", value, name),
|
||||
Delete: pulumi.Sprintf("cd /var/lib/secrets && rm %s", name),
|
||||
Environment: environment,
|
||||
}, pulumi.DependsOn(resources))
|
||||
}
|
||||
|
|
|
|||
93
iac/inskip.go
Normal file
93
iac/inskip.go
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
package iac
|
||||
|
||||
import (
|
||||
"github.com/pulumi/pulumi-cloudflare/sdk/v4/go/cloudflare"
|
||||
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
|
||||
)
|
||||
|
||||
func InskipPage(ctx *pulumi.Context) error {
|
||||
_, err := cloudflare.NewPagesProject(ctx, "inskip-root", &cloudflare.PagesProjectArgs{
|
||||
AccountId: pulumi.ID("0467b993b65d8fd4a53fe24ed2fbb2a1"),
|
||||
Name: pulumi.String("inskip-root"),
|
||||
ProductionBranch: pulumi.String("main"),
|
||||
BuildConfig: &cloudflare.PagesProjectBuildConfigArgs{
|
||||
BuildCommand: pulumi.String("hugo"),
|
||||
DestinationDir: pulumi.String("public"),
|
||||
RootDir: pulumi.String("/"),
|
||||
},
|
||||
Source: &cloudflare.PagesProjectSourceArgs{
|
||||
Type: pulumi.String("github"),
|
||||
Config: &cloudflare.PagesProjectSourceConfigArgs{
|
||||
DeploymentsEnabled: pulumi.Bool(true),
|
||||
Owner: pulumi.String("kittywitch"),
|
||||
PrCommentsEnabled: pulumi.Bool(false),
|
||||
PreviewBranchExcludes: pulumi.StringArray{
|
||||
pulumi.String("main"),
|
||||
pulumi.String("prod"),
|
||||
},
|
||||
PreviewBranchIncludes: pulumi.StringArray{
|
||||
pulumi.String("dev"),
|
||||
pulumi.String("preview"),
|
||||
},
|
||||
PreviewDeploymentSetting: pulumi.String("custom"),
|
||||
ProductionBranch: pulumi.String("main"),
|
||||
ProductionDeploymentEnabled: pulumi.Bool(true),
|
||||
RepoName: pulumi.String("inskip.me"),
|
||||
},
|
||||
},
|
||||
DeploymentConfigs: &cloudflare.PagesProjectDeploymentConfigsArgs{
|
||||
Preview: &cloudflare.PagesProjectDeploymentConfigsPreviewArgs{
|
||||
CompatibilityDate: pulumi.String("2022-08-15"),
|
||||
CompatibilityFlags: pulumi.StringArray{},
|
||||
/* D1Databases: pulumi.AnyMap{
|
||||
"D1BINDING": pulumi.Any("445e2955-951a-4358-a35b-a4d0c813f63"),
|
||||
},
|
||||
DurableObjectNamespaces: pulumi.AnyMap{
|
||||
"DOBINDING": pulumi.Any("5eb63bbbe01eeed093cb22bb8f5acdc3"),
|
||||
},
|
||||
EnvironmentVariables: pulumi.AnyMap{
|
||||
"ENVIRONMENT": pulumi.Any("preview"),
|
||||
},
|
||||
KvNamespaces: pulumi.AnyMap{
|
||||
"KVBINDING": pulumi.Any("5eb63bbbe01eeed093cb22bb8f5acdc3"),
|
||||
},
|
||||
R2Buckets: pulumi.AnyMap{
|
||||
"R2BINDING": pulumi.Any("some-bucket"),
|
||||
}, */
|
||||
},
|
||||
Production: &cloudflare.PagesProjectDeploymentConfigsProductionArgs{
|
||||
CompatibilityDate: pulumi.String("2022-08-16"),
|
||||
CompatibilityFlags: pulumi.StringArray{},
|
||||
/*D1Databases: pulumi.AnyMap{
|
||||
"D1BINDING1": pulumi.Any("445e2955-951a-4358-a35b-a4d0c813f63"),
|
||||
"D1BINDING2": pulumi.Any("a399414b-c697-409a-a688-377db6433cd9"),
|
||||
},
|
||||
DurableObjectNamespaces: pulumi.AnyMap{
|
||||
"DOBINDING1": pulumi.Any("5eb63bbbe01eeed093cb22bb8f5acdc3"),
|
||||
"DOBINDING2": pulumi.Any("3cdca5f8bb22bc390deee10ebbb36be5"),
|
||||
},
|
||||
EnvironmentVariables: pulumi.AnyMap{
|
||||
"ENVIRONMENT": pulumi.Any("production"),
|
||||
"OTHERVALUE": pulumi.Any("other value"),
|
||||
},
|
||||
KvNamespaces: pulumi.AnyMap{
|
||||
"KVBINDING1": pulumi.Any("5eb63bbbe01eeed093cb22bb8f5acdc3"),
|
||||
"KVBINDING2": pulumi.Any("3cdca5f8bb22bc390deee10ebbb36be5"),
|
||||
},
|
||||
R2Buckets: pulumi.AnyMap{
|
||||
"R2BINDING1": pulumi.Any("some-bucket"),
|
||||
"R2BINDING2": pulumi.Any("other-bucket"),
|
||||
},*/
|
||||
},
|
||||
},
|
||||
})
|
||||
_, err = cloudflare.NewPagesDomain(ctx, "inskip-root", &cloudflare.PagesDomainArgs{
|
||||
AccountId: pulumi.String("0467b993b65d8fd4a53fe24ed2fbb2a1"),
|
||||
Domain: pulumi.String("inskip.me"),
|
||||
ProjectName: pulumi.String("inskip-root"),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
147
iac/record.go
147
iac/record.go
|
|
@ -1,93 +1,102 @@
|
|||
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"
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"github.com/creasty/defaults"
|
||||
"github.com/pulumi/pulumi-cloudflare/sdk/v4/go/cloudflare"
|
||||
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type DNSRecordType string
|
||||
|
||||
const (
|
||||
A DNSRecordType = "a"
|
||||
AAAA = "aaaa"
|
||||
MX = "mx"
|
||||
TXT = "txt"
|
||||
CAA = "caa"
|
||||
A DNSRecordType = "a"
|
||||
AAAA = "aaaa"
|
||||
MX = "mx"
|
||||
TXT = "txt"
|
||||
CAA = "caa"
|
||||
CNAME = "cname"
|
||||
)
|
||||
|
||||
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"`
|
||||
CFRecord *cloudflare.Record
|
||||
Zone *Zone
|
||||
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)
|
||||
func (r *DNSRecord) UnmarshalYAML(unmarshal func(interface{}) error) (err error) {
|
||||
err = defaults.Set(r)
|
||||
|
||||
type plain DNSRecord
|
||||
if err := unmarshal((*plain)(r)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
type plain DNSRecord
|
||||
if err := unmarshal((*plain)(r)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *DNSRecord) getZone(Zone *cloudflare.Zone) (pulumi.StringOutput) {
|
||||
return Zone.ID().ToStringOutput()
|
||||
func (r *DNSRecord) getZone() pulumi.StringOutput {
|
||||
return r.Zone.CFZone.ID().ToStringOutput()
|
||||
}
|
||||
|
||||
func (r *DNSRecord) getName(ZoneName string, Zone *cloudflare.Zone) (string) {
|
||||
base := fmt.Sprintf("%s-%s-%s", ZoneName, r.Kind, r.Name)
|
||||
func (r *DNSRecord) getName() string {
|
||||
base := fmt.Sprintf("%s-%s-%s", r.Zone.Alias, 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)
|
||||
}
|
||||
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, CNAME:
|
||||
suffix = fmt.Sprintf("-%s", hashString)
|
||||
}
|
||||
|
||||
built := base + suffix
|
||||
return built
|
||||
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)
|
||||
func (r *DNSRecord) handle(ctx *pulumi.Context, zone *Zone) (err error) {
|
||||
r.Zone = zone
|
||||
var recordArgs *cloudflare.RecordArgs
|
||||
switch r.Kind {
|
||||
case CAA:
|
||||
recordArgs = &cloudflare.RecordArgs{
|
||||
ZoneId: r.getZone(),
|
||||
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(),
|
||||
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),
|
||||
}
|
||||
}
|
||||
r.CFRecord, err = cloudflare.NewRecord(ctx, r.getName(), recordArgs, pulumi.DependsOn([]pulumi.Resource{r.Zone.CFZone}))
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
91
iac/ssh.go
91
iac/ssh.go
|
|
@ -0,0 +1,91 @@
|
|||
package iac
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"fmt"
|
||||
tls "github.com/pulumi/pulumi-tls/sdk/v4/go/tls"
|
||||
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
|
||||
"golang.org/x/crypto/ssh"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ca_key *tls.PrivateKey,
|
||||
// ca_cert *tls.SelfSignedCert) (key *tls.PrivateKey,
|
||||
// ca_key, ca_cert, err := iac.GenerateTLSCA(ctx)
|
||||
|
||||
// parseprivatekey
|
||||
// newsignerfromkey
|
||||
|
||||
func MakeCertificate() ssh.Certificate {
|
||||
var newCert ssh.Certificate
|
||||
// The sign() method fills in Nonce for us
|
||||
newCert.Nonce = make([]byte, 32)
|
||||
return newCert
|
||||
}
|
||||
|
||||
func PrivateKeyOpenSSHToRSAPrivateKey(keyPEM string) (key *rsa.PrivateKey, err error) {
|
||||
key_int, err := ssh.ParseRawPrivateKey([]byte(keyPEM))
|
||||
key_raw := key_int.(*rsa.PrivateKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return key_raw, err
|
||||
}
|
||||
|
||||
func GenerateOpenSSHHost(caKey *tls.PrivateKey, userKey *tls.PrivateKey, keyID string, name string) (certificate pulumi.StringOutput, err error) {
|
||||
return GenerateOpenSSH(caKey, userKey, keyID, ssh.HostCert, name)
|
||||
}
|
||||
|
||||
func GenerateOpenSSHUser(caKey *tls.PrivateKey, userKey *tls.PrivateKey, keyID string, name string) (certificate pulumi.StringOutput, err error) {
|
||||
return GenerateOpenSSH(caKey, userKey, keyID, ssh.UserCert, name)
|
||||
}
|
||||
|
||||
func GenerateOpenSSH(caKey *tls.PrivateKey, userKey *tls.PrivateKey, keyID string, certType uint32, name string) (certificate pulumi.StringOutput, err error) {
|
||||
var caKeyPem *rsa.PrivateKey
|
||||
var signer ssh.Signer
|
||||
|
||||
newCert := caKey.PrivateKeyOpenssh.ApplyT(func(capriv string) (cert pulumi.StringOutput) {
|
||||
newCertS := userKey.PrivateKeyOpenssh.ApplyT(func(content string) (cert pulumi.String) {
|
||||
caKeyPem, err = PrivateKeyOpenSSHToRSAPrivateKey(capriv)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
signer, err = ssh.NewSignerFromKey(caKeyPem)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
newCert := MakeCertificate()
|
||||
newCert.CertType = certType
|
||||
key, err := PrivateKeyOpenSSHToRSAPrivateKey(content)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
newCert.Key, err = ssh.NewPublicKey(key.Public())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
newCert.Serial = 0
|
||||
newCert.KeyId = keyID
|
||||
newCert.ValidPrincipals = []string{fmt.Sprintf("%s.inskip.me", name)}
|
||||
newCert.ValidAfter = 60
|
||||
threemo, err := time.ParseDuration("730h")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
threemosecs := uint64(threemo.Seconds())
|
||||
newCert.ValidBefore = threemosecs
|
||||
err = newCert.SignCert(rand.Reader, signer)
|
||||
return pulumi.String(string(ssh.MarshalAuthorizedKey(&newCert)))
|
||||
}).(pulumi.StringOutput)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return newCertS
|
||||
}).(pulumi.StringOutput)
|
||||
|
||||
if err != nil {
|
||||
return pulumi.StringOutput{}, err
|
||||
}
|
||||
return newCert, err
|
||||
}
|
||||
110
iac/tailscale.go
110
iac/tailscale.go
|
|
@ -1,110 +0,0 @@
|
|||
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, index string, 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"])
|
||||
index = record_.getName("inskip", zones["inskip"])
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
return record, index, err
|
||||
}
|
||||
|
||||
func HandleTSRecord(ctx *pulumi.Context, zones map[string]*cloudflare.Zone, device tailscale.GetDevicesDevice) (new_records map[string]*cloudflare.Record, err error) {
|
||||
if device.User != "kat@inskip.me" {
|
||||
return nil, nil
|
||||
}
|
||||
new_records = make(map[string]*cloudflare.Record)
|
||||
name := strings.Split(device.Name, ".")[0]
|
||||
for _, address := range device.Addresses {
|
||||
new_record, index, err := MakeRecord(ctx, zones, name, address)
|
||||
new_records[index] = new_record
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return new_records, err
|
||||
}
|
||||
|
||||
func HandleTSRecords(ctx *pulumi.Context,
|
||||
tailnet *tailscale.GetDevicesResult,
|
||||
zones map[string]*cloudflare.Zone,
|
||||
input_records map[string]*cloudflare.Record,
|
||||
) (records map[string]*cloudflare.Record, err error) {
|
||||
for _, device := range tailnet.Devices {
|
||||
new_records, err := HandleTSRecord(ctx, zones, device)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for k,v := range new_records {
|
||||
input_records[k] = v
|
||||
}
|
||||
records = input_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,
|
||||
[]string{fmt.Sprintf("%s.inskip.me", name)},
|
||||
device.Addresses,
|
||||
)
|
||||
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 {
|
||||
if device.User != "kat@inskip.me" {
|
||||
continue
|
||||
}
|
||||
name := strings.Split(device.Name, ".")[0]
|
||||
keys[fmt.Sprintf("ts-%s-host-key", name)], crs[fmt.Sprintf("ts-%s-host-cr", name)], certs[fmt.Sprintf("ts-%s-host-cert", 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
54
iac/tls.go
|
|
@ -1,54 +0,0 @@
|
|||
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),
|
||||
}, pulumi.DependsOn([]pulumi.Resource{ca_key, ca_cert}))
|
||||
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"),
|
||||
},
|
||||
}, pulumi.DependsOn([]pulumi.Resource{ca_key, ca_cert, key}))
|
||||
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),
|
||||
}, pulumi.DependsOn([]pulumi.Resource{ca_key, ca_cert, key, cr}))
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
return key, cr, cert, err
|
||||
}
|
||||
67
iac/zone.go
67
iac/zone.go
|
|
@ -1,24 +1,63 @@
|
|||
package iac
|
||||
|
||||
import (
|
||||
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
|
||||
cloudflare "github.com/pulumi/pulumi-cloudflare/sdk/v4/go/cloudflare"
|
||||
cloudflare "github.com/pulumi/pulumi-cloudflare/sdk/v4/go/cloudflare"
|
||||
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Zone struct {
|
||||
Zone string `yaml:"name"`
|
||||
Records []DNSRecord `yaml:"records"`
|
||||
Context *pulumi.Context
|
||||
Alias string
|
||||
Zone string `yaml:"name"`
|
||||
ExtraRecords []DNSRecord `yaml:"records"`
|
||||
CFZone *cloudflare.Zone
|
||||
Devices []Device
|
||||
CertAuth CertificateAuthority
|
||||
DNSSec *cloudflare.ZoneDnssec
|
||||
}
|
||||
|
||||
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
|
||||
func (z *Zone) Handle(ctx *pulumi.Context) (err error) {
|
||||
z.Context = ctx
|
||||
z.Alias = strings.ReplaceAll(z.Zone, ".", "-")
|
||||
z.CFZone, err = cloudflare.NewZone(ctx, z.Alias, &cloudflare.ZoneArgs{
|
||||
AccountId: pulumi.ID("0467b993b65d8fd4a53fe24ed2fbb2a1"),
|
||||
Zone: pulumi.String(z.Zone),
|
||||
Plan: pulumi.String("free"),
|
||||
})
|
||||
if z.Alias == "inskip-me" {
|
||||
z.CertAuth = CertificateAuthority{}
|
||||
err = z.CertAuth.handle(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = z.handleTailscale()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, record := range z.ExtraRecords {
|
||||
err = record.handle(ctx, z)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (z *Zone) dnssec() (err error) {
|
||||
z.DNSSec, err = cloudflare.NewZoneDnssec(z.Context, z.Alias, &cloudflare.ZoneDnssecArgs{
|
||||
ZoneId: z.CFZone.ID(),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (z *Zone) handleTailscale() (err error) {
|
||||
tailnet := Tailnet{}
|
||||
err = tailnet.handle(z.Context, z, z.CertAuth.Key, z.CertAuth.Cert)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
z.Devices = tailnet.Devices
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
users.users.kat = {
|
||||
uid = 1000;
|
||||
isNormalUser = true;
|
||||
hashedPassword = "$6$G26zDwcywO6$YzHK1YI6X0d7x/mV6maCx6B7V3M1JdE3VqxxjNc7muxUPkZo0YYwniAB2";
|
||||
openssh.authorizedKeys = {
|
||||
inherit (tree.kat.user.data) keys;
|
||||
};
|
||||
|
|
|
|||
80
main.go
80
main.go
|
|
@ -1,68 +1,38 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
|
||||
"github.com/pulumi/pulumi-tailscale/sdk/go/tailscale"
|
||||
"gopkg.in/yaml.v3"
|
||||
"os"
|
||||
"kittywitch/iac"
|
||||
"github.com/pulumi/pulumi-command/sdk/go/command/local"
|
||||
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
|
||||
"gopkg.in/yaml.v3"
|
||||
"kittywitch/iac"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
katConfig := iac.KatConfig{}
|
||||
store := iac.KatConfig{}
|
||||
|
||||
configFile, err := os.ReadFile("config.yaml")
|
||||
configFile, err := os.ReadFile("config.yaml")
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := yaml.Unmarshal(configFile, &katConfig); err != nil {
|
||||
return
|
||||
}
|
||||
if err := yaml.Unmarshal(configFile, &store); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
pulumi.Run(func(ctx *pulumi.Context) error {
|
||||
tailnet, err := tailscale.GetDevices(ctx, &tailscale.GetDevicesArgs{}, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pulumi.Run(func(ctx *pulumi.Context) error {
|
||||
for _, zone := range store.Zones {
|
||||
err = zone.Handle(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// zones, dnssec, records
|
||||
zones, _, records, err := iac.HandleDNS(ctx, katConfig)
|
||||
err = iac.InskipPage(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
records, err = iac.HandleTSRecords(ctx, tailnet, zones, records)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ca_key, ca_cert, err := iac.GenerateTLSCA(ctx)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
keys, _, certs, err := iac.HandleTSHostCerts(ctx, tailnet, ca_key, ca_cert)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// files for those certs
|
||||
|
||||
files := make(map[string]*local.Command)
|
||||
|
||||
files, err = iac.PKITLSFiles(ctx, files, keys, certs)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return err
|
||||
})
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{ lib, config, inputs, ... }: let
|
||||
# TODO: convert to nix-std
|
||||
inherit (lib.attrsets) mapAttrsToList mapAttrs;
|
||||
inherit (lib.attrsets) mapAttrsToList mapAttrs filterAttrs;
|
||||
inherit (lib.lists) optionals;
|
||||
inherit (lib.options) mkOption;
|
||||
inherit (lib.types) int attrsOf submodule;
|
||||
|
|
@ -13,7 +13,7 @@
|
|||
maxJobs = 100;
|
||||
speedFactor = config.distributed.outputs.${name};
|
||||
supportedFeatures = [ "nixos-test" "benchmark" "big-parallel" "kvm" ];
|
||||
} ) (inputs.self.nixosConfigurations // inputs.self.darwinConfigurations);
|
||||
} ) (filterAttrs (n: _: n != config.networking.hostName) (inputs.self.nixosConfigurations // inputs.self.darwinConfigurations));
|
||||
daiyousei = {
|
||||
hostName = "daiyousei.inskip.me";
|
||||
sshUser = "root";
|
||||
|
|
|
|||
|
|
@ -9,6 +9,17 @@ _: let
|
|||
|
||||
distributed.systems.renko.preference = 5;
|
||||
|
||||
environment.systemPackages = with pkgs; [
|
||||
fd # fd, better fine!
|
||||
ripgrep # rg, better grep!
|
||||
go # Required for pulumi
|
||||
pulumi-bin # Infrastructure as code
|
||||
deadnix # dead-code scanner
|
||||
alejandra # code formatter
|
||||
statix # anti-pattern finder
|
||||
deploy-rs.deploy-rs # deployment system
|
||||
];
|
||||
|
||||
homebrew = {
|
||||
brewPrefix = "/opt/homebrew/bin";
|
||||
brews = [
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue