nixfiles/provider-openssh/pkg/provider/provider.go
2023-03-09 15:43:39 -08:00

133 lines
3.9 KiB
Go

package provider
import (
"crypto"
"crypto/ed25519"
"crypto/rand"
"crypto/rsa"
"strings"
"time"
"github.com/blang/semver"
p "github.com/pulumi/pulumi-go-provider"
"github.com/pulumi/pulumi-go-provider/infer"
"github.com/pulumi/pulumi-go-provider/integration"
"github.com/pulumi/pulumi-go-provider/middleware/schema"
"github.com/pulumi/pulumi/sdk/v3/go/common/tokens"
"golang.org/x/crypto/ssh"
)
func Provider() p.Provider {
return infer.Provider(infer.Options{
Metadata: schema.Metadata{
DisplayName: "OpenSSH Cert",
Description: "I hope the people who worked on pulumi stub their toe every day",
Keywords: []string{
"pulumi",
"openssh",
"category/utility",
"kind/native",
},
Homepage: "https://kittywit.ch",
License: "WTFPL",
Repository: "https://github.com/kittywitch/kittywitch",
Publisher: "Pulumi",
LogoURL: "https://raw.githubusercontent.com/pulumi/pulumi-command/master/assets/logo.svg",
// This contains language specific details for generating the provider's SDKs
LanguageMap: map[string]any{
"go": map[string]any{
"generateResourceContainerTypes": true,
"importBasePath": "github.com/kittywitch/provider-opensshcertificate/sdk/go/provider",
},
},
},
Resources: []infer.InferredResource{infer.Resource[*OpenSSHCertificate, OpenSSHCertificateArgs, OpenSSHCertificateState]()},
ModuleMap: map[tokens.ModuleName]tokens.ModuleName{
"opensshcertificate": "index",
},
})
}
type OpenSSHCertificate struct{}
type OpenSSHCertificateArgs struct {
Algorithm string `pulumi:"algorithm"`
Kind string `pulumi:"kind"`
Hostname string `pulumi:"hostname"`
CAPrivateKey string `pulumi:"cakey"`
UserPrivateKey string `pulumi:"userkey"`
Duration string `pulumi:"duration"`
}
type OpenSSHCertificateState struct {
OpenSSHCertificateArgs
Content string `pulumi:"content"`
}
func (c *OpenSSHCertificate) Create(ctx p.Context, name string, input OpenSSHCertificateArgs, preview bool) (string, OpenSSHCertificateState, error) {
state := OpenSSHCertificateState{OpenSSHCertificateArgs: input}
if preview {
return name, state, nil
}
caPrivateKeyInterface, err := ssh.ParseRawPrivateKey([]byte(input.CAPrivateKey))
if err != nil {
return name, state, err
}
userPrivateKeyInterface, err := ssh.ParseRawPrivateKey([]byte(input.UserPrivateKey))
if err != nil {
return name, state, err
}
var signer ssh.Signer
var userPublicKey crypto.PublicKey
caPrivateKey := caPrivateKeyInterface.(*rsa.PrivateKey)
switch input.Algorithm {
case "rsa":
userPrivateKey := userPrivateKeyInterface.(*rsa.PrivateKey)
userPublicKey = userPrivateKey.Public()
signer, err = ssh.NewSignerFromKey(caPrivateKey)
case "ed25519":
userPrivateKey := userPrivateKeyInterface.(*ed25519.PrivateKey)
userPublicKey = userPrivateKey.Public()
signer, err = ssh.NewSignerFromKey(caPrivateKey)
default:
panic("unsupported key algorithm")
}
if err != nil {
return name, state, err
}
var cert ssh.Certificate
switch input.Kind {
case "user":
cert.CertType = ssh.UserCert
case "host":
cert.CertType = ssh.HostCert
default:
panic("unsupported key kind")
}
cert.Key, err = ssh.NewPublicKey(userPublicKey)
if err != nil {
panic(err)
}
now := time.Now()
nowunix := uint64(now.Unix())
cert.Serial = nowunix
cert.KeyId = input.Hostname
cert.ValidPrincipals = []string{input.Hostname}
cert.ValidAfter = nowunix
duration, err := time.ParseDuration(input.Duration)
if err != nil {
panic(err)
}
cert.ValidBefore = uint64(now.Add(duration).Unix())
err = cert.SignCert(rand.Reader, signer)
state.Content = string(ssh.MarshalAuthorizedKey(&cert))
return name, state, nil
}
func Schema(version string) (string, error) {
if strings.HasPrefix(version, "v") {
version = version[1:]
}
s, err := integration.NewServer("opensshcertificate", semver.MustParse(version), Provider()).
GetSchema(p.GetSchemaRequest{})
return s.Schema, err
}