mirror of
https://github.com/kittywitch/nixfiles.git
synced 2026-02-09 12:29: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:
|
config:
|
||||||
cloudflare:apiToken:
|
cloudflare:apiToken:
|
||||||
secure: AAABACPPwldgPSVHPYdh4lQnYR+baaGsnQRwGw/qwNke8wuFnCTdZIqj6yXu0CCGaYBu0+66LTALNNl9mo9HXs/PMLmoOqLo
|
secure: AAABAFcufTX7tZZf2gcK6hML2tgovDEfcPAJcgjfYkV3GMS4Ilwzuco5p+hCpyj3vCm7cqm3tmdwOLlOFxGqKZGRj+ESXAzv
|
||||||
tailscale:apiKey:
|
tailscale:apiKey:
|
||||||
secure: AAABAGc7s7XJ+voSUNcMmRuVwrUdx3kojn0fdEl6qpUy0WmhgHbk6cEz2/kGSEGhuLGwo3mzOGVTI+NVu6/Xz4PmE9FME++VfE8cz5DFjDrMJ4JdX0DR
|
secure: AAABAGc7s7XJ+voSUNcMmRuVwrUdx3kojn0fdEl6qpUy0WmhgHbk6cEz2/kGSEGhuLGwo3mzOGVTI+NVu6/Xz4PmE9FME++VfE8cz5DFjDrMJ4JdX0DR
|
||||||
tailscale:tailnet: inskip.me
|
tailscale:tailnet: inskip.me
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,8 @@ zones:
|
||||||
flags: 0
|
flags: 0
|
||||||
tag: iodef
|
tag: iodef
|
||||||
value: mailto:acme@inskip.me
|
value: mailto:acme@inskip.me
|
||||||
|
- kind: cname
|
||||||
|
value: inskip-root.pages.dev
|
||||||
- kind: caa
|
- kind: caa
|
||||||
flags: 0
|
flags: 0
|
||||||
tag: issue
|
tag: issue
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,16 @@
|
||||||
_: {
|
{ pkgs, ... }: {
|
||||||
nix.envVars = {
|
nix.envVars = {
|
||||||
"SSH_AUTH_SOCK" = "/Users/kat/.gnupg/S.gpg-agent.ssh";
|
"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": {
|
"arcexprs": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1674878824,
|
"lastModified": 1675107003,
|
||||||
"narHash": "sha256-skuyKtydzGFtd2UQB2BZW2bMaUcS+fvHEEz+H4MNQ4g=",
|
"narHash": "sha256-ao5OLwhC7+T3O2ixRrt76gIg9uZR3c17SJL3Wvx0EpQ=",
|
||||||
"owner": "arcnmx",
|
"owner": "arcnmx",
|
||||||
"repo": "nixexprs",
|
"repo": "nixexprs",
|
||||||
"rev": "ab1bd348da95d6f33ad28992b5d8c636d2330cc9",
|
"rev": "5b7d1eb5e578da7ed36b2105f80f82f9ad11244d",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|
@ -126,11 +126,11 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1674771519,
|
"lastModified": 1675181178,
|
||||||
"narHash": "sha256-U0W3S1nX6yEvLh3Vq70EORbmXecAKXfmEfCfaA4A+I8=",
|
"narHash": "sha256-jymSUUjKoArptU7LJ1i4boysXptnpuETiUTenKgs2fM=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "home-manager",
|
"repo": "home-manager",
|
||||||
"rev": "bb4b25b302dbf0f527f190461b080b5262871756",
|
"rev": "69696fe53940562a047bf2ec675cc1dcd1bc09b3",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|
@ -173,11 +173,11 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1674357402,
|
"lastModified": 1674962474,
|
||||||
"narHash": "sha256-oxOCYORXBh0KW4KEwKpzs2LThDdVmEwREV+SsP4CIpg=",
|
"narHash": "sha256-qEXdgW5fnMSdQwP1zQYa0fVtI0f3G1f2qNRjUEherCs=",
|
||||||
"owner": "Mic92",
|
"owner": "Mic92",
|
||||||
"repo": "nix-index-database",
|
"repo": "nix-index-database",
|
||||||
"rev": "e9e7c5c62965e7e656febb5ba578d53f751eb41f",
|
"rev": "a385f6192f5471c4cebeeb0d2e966b5ccf123df5",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|
@ -188,11 +188,11 @@
|
||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1674641431,
|
"lastModified": 1675115703,
|
||||||
"narHash": "sha256-qfo19qVZBP4qn5M5gXc/h1MDgAtPA5VxJm9s8RUAkVk=",
|
"narHash": "sha256-4zetAPSyY0D77x+Ww9QBe8RHn1akvIvHJ/kgg8kGDbk=",
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "9b97ad7b4330aacda9b2343396eb3df8a853b4fc",
|
"rev": "2caf4ef5005ecc68141ecb4aac271079f7371c44",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|
@ -205,11 +205,11 @@
|
||||||
"pypi-deps-db": {
|
"pypi-deps-db": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1674855014,
|
"lastModified": 1675156620,
|
||||||
"narHash": "sha256-SMUq72uWWs+KAlcRB7UXzI1QHNTGiUTpQK2qj1evHXQ=",
|
"narHash": "sha256-lmnsBYJz2Fgm0WFNUgSqskpwa0ffbFOr9YGDZpUXptk=",
|
||||||
"owner": "DavHau",
|
"owner": "DavHau",
|
||||||
"repo": "pypi-deps-db",
|
"repo": "pypi-deps-db",
|
||||||
"rev": "a375715227007ca768d372b2b09bcb76f8f19d78",
|
"rev": "2e236bb32e6e3ef13cd56fc6d9aee8c89059a1ac",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|
|
||||||
75
iac/ca.go
75
iac/ca.go
|
|
@ -1,40 +1,45 @@
|
||||||
package iac
|
package iac
|
||||||
|
|
||||||
import(
|
import (
|
||||||
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
|
tls "github.com/pulumi/pulumi-tls/sdk/v4/go/tls"
|
||||||
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) {
|
type CertificateAuthority struct {
|
||||||
key, err = tls.NewPrivateKey(ctx, "kat-root-ca-key", &tls.PrivateKeyArgs{
|
Key *tls.PrivateKey
|
||||||
Algorithm: pulumi.String("RSA"),
|
Cert *tls.SelfSignedCert
|
||||||
RsaBits: pulumi.Int(4096),
|
}
|
||||||
})
|
|
||||||
|
func (ca *CertificateAuthority) handle(ctx *pulumi.Context) (err error) {
|
||||||
if err != nil {
|
ca.Key, err = tls.NewPrivateKey(ctx, "ca-root", &tls.PrivateKeyArgs{
|
||||||
return nil, nil, err
|
Algorithm: pulumi.String("RSA"),
|
||||||
}
|
RsaBits: pulumi.Int(4096),
|
||||||
|
})
|
||||||
cert, err = tls.NewSelfSignedCert(ctx, "kat-root-ca-pem-cert", &tls.SelfSignedCertArgs{
|
|
||||||
PrivateKeyPem: key.PrivateKeyPem,
|
if err != nil {
|
||||||
AllowedUses: goStringArrayToPulumiStringArray([]string{"digital_signature",
|
return err
|
||||||
"cert_signing",
|
}
|
||||||
"crl_signing"}),
|
|
||||||
IsCaCertificate: pulumi.Bool(true),
|
ca.Cert, err = tls.NewSelfSignedCert(ctx, "ca-root", &tls.SelfSignedCertArgs{
|
||||||
ValidityPeriodHours: pulumi.Int(2562047),
|
PrivateKeyPem: ca.Key.PrivateKeyPem,
|
||||||
Subject: &tls.SelfSignedCertSubjectArgs{
|
AllowedUses: goStringArrayToPulumiStringArray([]string{"digital_signature",
|
||||||
CommonName: pulumi.String("inskip.me"),
|
"cert_signing",
|
||||||
Organization: pulumi.String("Kat Inskip"),
|
"crl_signing"}),
|
||||||
},
|
IsCaCertificate: pulumi.Bool(true),
|
||||||
})
|
ValidityPeriodHours: pulumi.Int(2562047),
|
||||||
|
Subject: &tls.SelfSignedCertSubjectArgs{
|
||||||
if err != nil {
|
CommonName: pulumi.String("inskip.me"),
|
||||||
return nil, nil, err
|
Organization: pulumi.String("Kat Inskip"),
|
||||||
}
|
},
|
||||||
|
})
|
||||||
ctx.Export("tls_ca_pem_key", key.PrivateKeyPem)
|
|
||||||
ctx.Export("tls_ca_os_key", key.PrivateKeyOpenssh)
|
if err != nil {
|
||||||
ctx.Export("tls_ca_cert", cert.CertPem)
|
return err
|
||||||
|
}
|
||||||
return key, cert, 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
|
package iac
|
||||||
|
|
||||||
type KatConfig struct {
|
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
|
package iac
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/pulumi/pulumi-command/sdk/go/command/local"
|
"github.com/pulumi/pulumi-command/sdk/go/command/remote"
|
||||||
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
|
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
|
||||||
"github.com/pulumi/pulumi-tls/sdk/v4/go/tls"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func createPulumiFile(ctx *pulumi.Context, name string, value pulumi.StringOutput, resource pulumi.Resource) (*local.Command, error) {
|
func CreatePulumiFile(ctx *pulumi.Context, name string, fqdn string, value pulumi.StringOutput, resources []pulumi.Resource) (*remote.Command, error) {
|
||||||
repo_root := os.Getenv("REPO_ROOT")
|
environment := goMapToPulumiMap(map[string]string{
|
||||||
data_root := path.Join(repo_root, "./data")
|
"PULUMI_SKIP_UPDATE_CHECK": "true",
|
||||||
ctx.Export(name, value)
|
})
|
||||||
return local.NewCommand(ctx, name, &local.CommandArgs{
|
return remote.NewCommand(ctx, name, &remote.CommandArgs{
|
||||||
Create: pulumi.String(fmt.Sprintf("pulumi stack output %s --non-interactive --show-secrets > %s", name, name)),
|
Connection: &remote.ConnectionArgs{
|
||||||
Update: pulumi.String(fmt.Sprintf("pulumi stack output %s --non-interactive --show-secrets > %s", name, name)),
|
Host: pulumi.String(fqdn),
|
||||||
Delete: pulumi.String(fmt.Sprintf("rm %s", name)),
|
Port: pulumi.Float64Ptr(22),
|
||||||
Dir: pulumi.String(data_root),
|
User: pulumi.String("deploy"),
|
||||||
Environment: goMapToPulumiMap(map[string]string{
|
AgentSocketPath: pulumi.String("/Users/kat/.gnupg/S.gpg-agent.ssh"),
|
||||||
"PULUMI_SKIP_UPDATE_CHECK": "true",
|
},
|
||||||
}),
|
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),
|
||||||
}, pulumi.DependsOn([]pulumi.Resource{resource}))
|
Delete: pulumi.Sprintf("cd /var/lib/secrets && rm %s", name),
|
||||||
}
|
Environment: environment,
|
||||||
|
}, pulumi.DependsOn(resources))
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
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
|
package iac
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
|
"crypto/md5"
|
||||||
cloudflare "github.com/pulumi/pulumi-cloudflare/sdk/v4/go/cloudflare"
|
"encoding/hex"
|
||||||
"github.com/creasty/defaults"
|
"fmt"
|
||||||
"fmt"
|
"github.com/creasty/defaults"
|
||||||
"strings"
|
"github.com/pulumi/pulumi-cloudflare/sdk/v4/go/cloudflare"
|
||||||
"crypto/md5"
|
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
|
||||||
"encoding/hex"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type DNSRecordType string
|
type DNSRecordType string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
A DNSRecordType = "a"
|
A DNSRecordType = "a"
|
||||||
AAAA = "aaaa"
|
AAAA = "aaaa"
|
||||||
MX = "mx"
|
MX = "mx"
|
||||||
TXT = "txt"
|
TXT = "txt"
|
||||||
CAA = "caa"
|
CAA = "caa"
|
||||||
|
CNAME = "cname"
|
||||||
)
|
)
|
||||||
|
|
||||||
type DNSRecord struct {
|
type DNSRecord struct {
|
||||||
Name string `default:"@" yaml:"name"`
|
CFRecord *cloudflare.Record
|
||||||
Kind DNSRecordType `yaml:"kind"`
|
Zone *Zone
|
||||||
Value string `yaml:"value,omitempty"`
|
Name string `default:"@" yaml:"name"`
|
||||||
Priority int `yaml:"priority,omitempty"`
|
Kind DNSRecordType `yaml:"kind"`
|
||||||
Flags string `yaml:"flags,omitempty"`
|
Value string `yaml:"value,omitempty"`
|
||||||
Tag string `yaml:"tag,omitempty"`
|
Priority int `yaml:"priority,omitempty"`
|
||||||
Ttl int `default:"3600" yaml:"ttl,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 {
|
func (r *DNSRecord) UnmarshalYAML(unmarshal func(interface{}) error) (err error) {
|
||||||
defaults.Set(r)
|
err = defaults.Set(r)
|
||||||
|
|
||||||
type plain DNSRecord
|
if err != nil {
|
||||||
if err := unmarshal((*plain)(r)); err != nil {
|
return err
|
||||||
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) {
|
func (r *DNSRecord) getZone() pulumi.StringOutput {
|
||||||
return Zone.ID().ToStringOutput()
|
return r.Zone.CFZone.ID().ToStringOutput()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *DNSRecord) getName(ZoneName string, Zone *cloudflare.Zone) (string) {
|
func (r *DNSRecord) getName() string {
|
||||||
base := fmt.Sprintf("%s-%s-%s", ZoneName, r.Kind, r.Name)
|
base := fmt.Sprintf("%s-%s-%s", r.Zone.Alias, r.Kind, r.Name)
|
||||||
|
|
||||||
hash := md5.Sum([]byte(r.Value))
|
hash := md5.Sum([]byte(r.Value))
|
||||||
hashString := hex.EncodeToString(hash[:])[:5]
|
hashString := hex.EncodeToString(hash[:])[:5]
|
||||||
suffix := ""
|
suffix := ""
|
||||||
switch r.Kind {
|
switch r.Kind {
|
||||||
case MX:
|
case MX:
|
||||||
suffix = fmt.Sprintf("-%d-%s", r.Priority, hashString)
|
suffix = fmt.Sprintf("-%d-%s", r.Priority, hashString)
|
||||||
case CAA:
|
case CAA:
|
||||||
suffix = fmt.Sprintf("%s-%s", r.Flags, r.Tag)
|
suffix = fmt.Sprintf("%s-%s", r.Flags, r.Tag)
|
||||||
case A, AAAA, TXT:
|
case A, AAAA, TXT, CNAME:
|
||||||
suffix = fmt.Sprintf("-%s", hashString)
|
suffix = fmt.Sprintf("-%s", hashString)
|
||||||
}
|
}
|
||||||
|
|
||||||
built := base + suffix
|
built := base + suffix
|
||||||
return built
|
return built
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *DNSRecord) handle(ctx *pulumi.Context, ZoneName string, zone *cloudflare.Zone) (*cloudflare.Record, error) {
|
func (r *DNSRecord) handle(ctx *pulumi.Context, zone *Zone) (err error) {
|
||||||
var recordArgs *cloudflare.RecordArgs
|
r.Zone = zone
|
||||||
switch r.Kind {
|
var recordArgs *cloudflare.RecordArgs
|
||||||
case CAA:
|
switch r.Kind {
|
||||||
recordArgs = &cloudflare.RecordArgs{
|
case CAA:
|
||||||
ZoneId: r.getZone(zone),
|
recordArgs = &cloudflare.RecordArgs{
|
||||||
Name: pulumi.String(r.Name),
|
ZoneId: r.getZone(),
|
||||||
Type: pulumi.String(strings.ToUpper(string(r.Kind))),
|
Name: pulumi.String(r.Name),
|
||||||
Ttl: pulumi.Int(r.Ttl),
|
Type: pulumi.String(strings.ToUpper(string(r.Kind))),
|
||||||
Data: &cloudflare.RecordDataArgs{
|
Ttl: pulumi.Int(r.Ttl),
|
||||||
Flags: pulumi.String(r.Flags),
|
Data: &cloudflare.RecordDataArgs{
|
||||||
Tag: pulumi.String(r.Tag),
|
Flags: pulumi.String(r.Flags),
|
||||||
Value: pulumi.String(r.Value),
|
Tag: pulumi.String(r.Tag),
|
||||||
},
|
Value: pulumi.String(r.Value),
|
||||||
}
|
},
|
||||||
default:
|
}
|
||||||
recordArgs = &cloudflare.RecordArgs{
|
default:
|
||||||
ZoneId: r.getZone(zone),
|
recordArgs = &cloudflare.RecordArgs{
|
||||||
Name: pulumi.String(r.Name),
|
ZoneId: r.getZone(),
|
||||||
Type: pulumi.String(strings.ToUpper(string(r.Kind))),
|
Name: pulumi.String(r.Name),
|
||||||
Ttl: pulumi.Int(r.Ttl),
|
Type: pulumi.String(strings.ToUpper(string(r.Kind))),
|
||||||
Priority: pulumi.Int(r.Priority),
|
Ttl: pulumi.Int(r.Ttl),
|
||||||
Value: pulumi.String(r.Value),
|
Priority: pulumi.Int(r.Priority),
|
||||||
}
|
Value: pulumi.String(r.Value),
|
||||||
}
|
}
|
||||||
return cloudflare.NewRecord(ctx, r.getName(ZoneName, zone), recordArgs)
|
}
|
||||||
|
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
|
package iac
|
||||||
|
|
||||||
import (
|
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 {
|
type Zone struct {
|
||||||
Zone string `yaml:"name"`
|
Context *pulumi.Context
|
||||||
Records []DNSRecord `yaml:"records"`
|
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) {
|
func (z *Zone) Handle(ctx *pulumi.Context) (err error) {
|
||||||
zone, err = cloudflare.NewZone(ctx, name, &cloudflare.ZoneArgs{
|
z.Context = ctx
|
||||||
AccountId: pulumi.ID("0467b993b65d8fd4a53fe24ed2fbb2a1"),
|
z.Alias = strings.ReplaceAll(z.Zone, ".", "-")
|
||||||
Zone: pulumi.String(z.Zone),
|
z.CFZone, err = cloudflare.NewZone(ctx, z.Alias, &cloudflare.ZoneArgs{
|
||||||
Plan: pulumi.String("free"),
|
AccountId: pulumi.ID("0467b993b65d8fd4a53fe24ed2fbb2a1"),
|
||||||
})
|
Zone: pulumi.String(z.Zone),
|
||||||
if err != nil {
|
Plan: pulumi.String("free"),
|
||||||
return nil, err
|
})
|
||||||
}
|
if z.Alias == "inskip-me" {
|
||||||
return zone, err
|
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 = {
|
users.users.kat = {
|
||||||
uid = 1000;
|
uid = 1000;
|
||||||
isNormalUser = true;
|
isNormalUser = true;
|
||||||
|
hashedPassword = "$6$G26zDwcywO6$YzHK1YI6X0d7x/mV6maCx6B7V3M1JdE3VqxxjNc7muxUPkZo0YYwniAB2";
|
||||||
openssh.authorizedKeys = {
|
openssh.authorizedKeys = {
|
||||||
inherit (tree.kat.user.data) keys;
|
inherit (tree.kat.user.data) keys;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
80
main.go
80
main.go
|
|
@ -1,68 +1,38 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
|
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
|
||||||
"github.com/pulumi/pulumi-tailscale/sdk/go/tailscale"
|
"gopkg.in/yaml.v3"
|
||||||
"gopkg.in/yaml.v3"
|
"kittywitch/iac"
|
||||||
"os"
|
"os"
|
||||||
"kittywitch/iac"
|
|
||||||
"github.com/pulumi/pulumi-command/sdk/go/command/local"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
katConfig := iac.KatConfig{}
|
store := iac.KatConfig{}
|
||||||
|
|
||||||
configFile, err := os.ReadFile("config.yaml")
|
configFile, err := os.ReadFile("config.yaml")
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := yaml.Unmarshal(configFile, &katConfig); err != nil {
|
if err := yaml.Unmarshal(configFile, &store); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
pulumi.Run(func(ctx *pulumi.Context) error {
|
pulumi.Run(func(ctx *pulumi.Context) error {
|
||||||
tailnet, err := tailscale.GetDevices(ctx, &tailscale.GetDevicesArgs{}, nil)
|
for _, zone := range store.Zones {
|
||||||
if err != nil {
|
err = zone.Handle(ctx)
|
||||||
return err
|
if err != nil {
|
||||||
}
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// zones, dnssec, records
|
err = iac.InskipPage(ctx)
|
||||||
zones, _, records, err := iac.HandleDNS(ctx, katConfig)
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
return err
|
||||||
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
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{ lib, config, inputs, ... }: let
|
{ lib, config, inputs, ... }: let
|
||||||
# TODO: convert to nix-std
|
# TODO: convert to nix-std
|
||||||
inherit (lib.attrsets) mapAttrsToList mapAttrs;
|
inherit (lib.attrsets) mapAttrsToList mapAttrs filterAttrs;
|
||||||
inherit (lib.lists) optionals;
|
inherit (lib.lists) optionals;
|
||||||
inherit (lib.options) mkOption;
|
inherit (lib.options) mkOption;
|
||||||
inherit (lib.types) int attrsOf submodule;
|
inherit (lib.types) int attrsOf submodule;
|
||||||
|
|
@ -13,7 +13,7 @@
|
||||||
maxJobs = 100;
|
maxJobs = 100;
|
||||||
speedFactor = config.distributed.outputs.${name};
|
speedFactor = config.distributed.outputs.${name};
|
||||||
supportedFeatures = [ "nixos-test" "benchmark" "big-parallel" "kvm" ];
|
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 = {
|
daiyousei = {
|
||||||
hostName = "daiyousei.inskip.me";
|
hostName = "daiyousei.inskip.me";
|
||||||
sshUser = "root";
|
sshUser = "root";
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,17 @@ _: let
|
||||||
|
|
||||||
distributed.systems.renko.preference = 5;
|
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 = {
|
homebrew = {
|
||||||
brewPrefix = "/opt/homebrew/bin";
|
brewPrefix = "/opt/homebrew/bin";
|
||||||
brews = [
|
brews = [
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue