support encryption
Some checks failed
tests / golangci-lint (push) Failing after 20s
tests / go test (push) Successful in 41s

This commit is contained in:
Jared Allard 2025-03-01 10:09:26 -08:00
parent db95258d63
commit eb74b6ea15
Signed by: jaredallard
SSH key fingerprint: SHA256:wyRyyv28jBYw8Yp/oABNPUYvbGd6hyZj23XVXEm5G/U
13 changed files with 463 additions and 53 deletions

1
.gitignore vendored
View file

@ -49,4 +49,5 @@ CHANGELOG.md
## <<Stencil::Block(custom)>> ## <<Stencil::Block(custom)>>
data/ data/
!data/.gitkeep !data/.gitkeep
*.key
## <</Stencil::Block>> ## <</Stencil::Block>>

View file

@ -27,11 +27,7 @@ import (
) )
func main() { func main() {
exitCode := 0
defer func() { os.Exit(exitCode) }()
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill) ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill)
defer cancel()
rootCmd := &cobra.Command{ rootCmd := &cobra.Command{
Use: "klefkictl", Use: "klefkictl",
@ -41,9 +37,12 @@ func main() {
newNewCommand(), newNewCommand(),
newListCommand(), newListCommand(),
newDeleteCommand(), newDeleteCommand(),
newRequestsCommand(),
) )
if err := rootCmd.ExecuteContext(ctx); err != nil { if err := rootCmd.ExecuteContext(ctx); err != nil {
fmt.Fprintln(os.Stderr, err) fmt.Fprintln(os.Stderr, err)
exitCode = 1 os.Exit(1)
} }
cancel()
} }

View file

@ -0,0 +1,124 @@
// Copyright (C) 2025 klefki contributors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//
// SPDX-License-Identifier: AGPL-3.0
package main
import (
"bytes"
"crypto/ed25519"
"fmt"
"io"
"os"
pbgrpcv1 "git.rgst.io/homelab/klefki/internal/server/grpc/generated/go/rgst/klefki/v1"
"git.rgst.io/homelab/klefki/internal/machines"
"git.rgst.io/homelab/sigtool/v3/sign"
"github.com/spf13/cobra"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
// newRequestsCommand creates a requests [cobra.Command]
func newRequestsCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "requests",
Short: "Make requests to a klefki server",
}
cmd.AddCommand(newGetKeyRequestCommand())
return cmd
}
// nopWriteCloser is a no-op [io.WriteCloser]
type nopWriteCloser struct {
io.Writer
}
// Close implements [io.Closer]
func (nwc nopWriteCloser) Close() error {
return nil
}
// newNopWriteCloser creates a new nopWriteCloser
func newNopWriteCloser(w io.Writer) *nopWriteCloser {
return &nopWriteCloser{w}
}
// newGetKeyRequestCommand creates a getkeyrequest [cobra.Command]
func newGetKeyRequestCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "getkeyrequest",
Short: "Get the passphrase for the given machine",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, _ []string) error {
privKeyPath := cmd.Flag("priv-key").Value.String()
privKeyByt, err := os.ReadFile(privKeyPath)
if err != nil {
return err
}
pk, err := machines.DecodePrivateKey(privKeyByt)
if err != nil {
return err
}
machineID, err := machines.Fingerprint(pk.Public().(ed25519.PublicKey))
if err != nil {
return fmt.Errorf("failed to get fingerprint for key: %w", err)
}
spk, err := sign.PrivateKeyFromBytes(pk)
if err != nil {
return fmt.Errorf("failed to create private key for decryption: %w", err)
}
fmt.Printf("Sending GetKeyRequest: machine_id=%s\n", machineID)
// TODO(jaredallard): Make a client
conn, err := grpc.NewClient("127.0.0.1:5300", grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
return fmt.Errorf("failed to connect to klefki server: %w", err)
}
defer conn.Close()
client := pbgrpcv1.NewKlefkiServiceClient(conn)
req := &pbgrpcv1.GetKeyRequest{}
req.SetMachineId(machineID)
req.SetNonce("FIXME")
req.SetSignature(ed25519.Sign(pk, []byte(req.GetNonce())))
resp, err := client.GetKey(cmd.Context(), req)
if err != nil {
return fmt.Errorf("failed to get key from server: %w", err)
}
dec, err := sign.NewDecryptor(bytes.NewReader(resp.GetKey()))
if err != nil {
return fmt.Errorf("failed to create decryptor: %w", err)
}
if err := dec.SetPrivateKey(spk, nil); err != nil {
return fmt.Errorf("failed to set private key on decryptor: %w", err)
}
return dec.Decrypt(os.Stdout)
},
}
flags := cmd.Flags()
flags.String("priv-key", "", "path to private key")
return cmd
}

View file

@ -39,11 +39,14 @@ disk.
### Security ### Security
- Pass-phrases are encrypted using to public key of the authenticated - Pass-phrases are encrypted to public key of the authenticated machine
machine to prevent the pass-phrase from ever being send unencrypted or to prevent the pass-phrase from ever being sent unencrypted or being
being able to decrypted the key. able to decrypted the key.
- Machine IDs are derived from the authenticated machine, through a - Machine IDs are derived from the authenticated machine, through a
signature check (public keys are stored on the server side). signature check (public keys are stored on the server side).
- This technically is vulnerable to replay attacks. However, the
returned data is encrypted to the key holder. An attacker replaying
this would get encrypted data only.
### Flow ### Flow

13
go.mod
View file

@ -1,11 +1,10 @@
module git.rgst.io/homelab/klefki module git.rgst.io/homelab/klefki
go 1.23.0 go 1.24
toolchain go1.24.0
require ( require (
entgo.io/ent v0.14.3 entgo.io/ent v0.14.3
git.rgst.io/homelab/sigtool/v3 v3.2.3-jaredallard.2
github.com/ncruces/go-sqlite3 v0.24.0 github.com/ncruces/go-sqlite3 v0.24.0
github.com/spf13/cobra v1.9.1 github.com/spf13/cobra v1.9.1
google.golang.org/grpc v1.70.0 google.golang.org/grpc v1.70.0
@ -17,6 +16,7 @@ require (
github.com/agext/levenshtein v1.2.3 // indirect github.com/agext/levenshtein v1.2.3 // indirect
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
github.com/bmatcuk/doublestar v1.3.4 // indirect github.com/bmatcuk/doublestar v1.3.4 // indirect
github.com/dchest/bcrypt_pbkdf v0.0.0-20150205184540-83f37f9c154a // indirect
github.com/go-openapi/inflect v0.21.0 // indirect github.com/go-openapi/inflect v0.21.0 // indirect
github.com/google/go-cmp v0.7.0 // indirect github.com/google/go-cmp v0.7.0 // indirect
github.com/google/uuid v1.6.0 // indirect github.com/google/uuid v1.6.0 // indirect
@ -26,11 +26,17 @@ require (
github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect
github.com/ncruces/julianday v1.0.0 // indirect github.com/ncruces/julianday v1.0.0 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/opencoff/go-fio v0.5.13 // indirect
github.com/opencoff/go-mmap v0.1.5 // indirect
github.com/pkg/xattr v0.4.10 // indirect
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
github.com/puzpuzpuz/xsync/v3 v3.5.1 // indirect
github.com/rivo/uniseg v0.2.0 // indirect github.com/rivo/uniseg v0.2.0 // indirect
github.com/spf13/pflag v1.0.6 // indirect github.com/spf13/pflag v1.0.6 // indirect
github.com/tetratelabs/wazero v1.9.0 // indirect github.com/tetratelabs/wazero v1.9.0 // indirect
github.com/zclconf/go-cty v1.16.2 // indirect github.com/zclconf/go-cty v1.16.2 // indirect
github.com/zclconf/go-cty-yaml v1.1.0 // indirect github.com/zclconf/go-cty-yaml v1.1.0 // indirect
golang.org/x/crypto v0.35.0 // indirect
golang.org/x/mod v0.23.0 // indirect golang.org/x/mod v0.23.0 // indirect
golang.org/x/net v0.35.0 // indirect golang.org/x/net v0.35.0 // indirect
golang.org/x/sync v0.11.0 // indirect golang.org/x/sync v0.11.0 // indirect
@ -38,6 +44,7 @@ require (
golang.org/x/text v0.22.0 // indirect golang.org/x/text v0.22.0 // indirect
golang.org/x/tools v0.30.0 // indirect golang.org/x/tools v0.30.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20241202173237-19429a94021a // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20241202173237-19429a94021a // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
) )
tool entgo.io/ent/cmd/ent tool entgo.io/ent/cmd/ent

26
go.sum generated
View file

@ -2,6 +2,8 @@ ariga.io/atlas v0.31.1-0.20250212144724-069be8033e83 h1:nX4HXncwIdvQ8/8sIUIf1nyC
ariga.io/atlas v0.31.1-0.20250212144724-069be8033e83/go.mod h1:Oe1xWPuu5q9LzyrWfbZmEZxFYeu4BHTyzfjeW2aZp/w= ariga.io/atlas v0.31.1-0.20250212144724-069be8033e83/go.mod h1:Oe1xWPuu5q9LzyrWfbZmEZxFYeu4BHTyzfjeW2aZp/w=
entgo.io/ent v0.14.3 h1:wokAV/kIlH9TeklJWGGS7AYJdVckr0DloWjIcO9iIIQ= entgo.io/ent v0.14.3 h1:wokAV/kIlH9TeklJWGGS7AYJdVckr0DloWjIcO9iIIQ=
entgo.io/ent v0.14.3/go.mod h1:aDPE/OziPEu8+OWbzy4UlvWmD2/kbRuWfK2A40hcxJM= entgo.io/ent v0.14.3/go.mod h1:aDPE/OziPEu8+OWbzy4UlvWmD2/kbRuWfK2A40hcxJM=
git.rgst.io/homelab/sigtool/v3 v3.2.3-jaredallard.2 h1:uZBtO7FkSvLoR9lgw36StTihkLKh8Rcc9QiAZW+z6gA=
git.rgst.io/homelab/sigtool/v3 v3.2.3-jaredallard.2/go.mod h1:0krgyBHYyZgmdfc9yzP+QVHlEvw+6bKKrSyt8a7A8oE=
github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60=
github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo=
@ -13,6 +15,8 @@ github.com/bmatcuk/doublestar v1.3.4/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dchest/bcrypt_pbkdf v0.0.0-20150205184540-83f37f9c154a h1:saTgr5tMLFnmy/yg3qDTft4rE5DY2uJ/cCxCe3q0XTU=
github.com/dchest/bcrypt_pbkdf v0.0.0-20150205184540-83f37f9c154a/go.mod h1:Bw9BbhOJVNR+t0jCqx2GC6zv0TGBsShs56Y3gfSCvl0=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
@ -44,8 +48,18 @@ github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt
github.com/ncruces/julianday v1.0.0/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g= github.com/ncruces/julianday v1.0.0/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g=
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/opencoff/go-fio v0.5.13 h1:j0vcntRgk475Lw27FOZ35Vw0U7zEY6UFCDfXKynXisY=
github.com/opencoff/go-fio v0.5.13/go.mod h1:mehrXmBVDrLdmPrzeuihR1Fv9SnAo+P+riSQybhOg3k=
github.com/opencoff/go-mmap v0.1.5 h1:RKPtevC4mOW5bi9skBPPo4nFTIH4lVWAL20Tff+FjLg=
github.com/opencoff/go-mmap v0.1.5/go.mod h1:y/6Jk/tDUc00k3oSQpiJX++20Nw7xFSlc5kLkhGnRXw=
github.com/pkg/xattr v0.4.10 h1:Qe0mtiNFHQZ296vRgUjRCoPHPqH7VdTOrZx3g0T+pGA=
github.com/pkg/xattr v0.4.10/go.mod h1:di8WF84zAKk8jzR1UBTEWh9AUlIZZ7M/JNt8e9B6ktU=
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/puzpuzpuz/xsync/v3 v3.5.1 h1:GJYJZwO6IdxN/IKbneznS6yPkVC+c3zyY/j19c++5Fg=
github.com/puzpuzpuz/xsync/v3 v3.5.1/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
@ -53,8 +67,8 @@ github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/tetratelabs/wazero v1.9.0 h1:IcZ56OuxrtaEz8UYNRHBrUa9bYeX9oVY93KspZZBf/I= github.com/tetratelabs/wazero v1.9.0 h1:IcZ56OuxrtaEz8UYNRHBrUa9bYeX9oVY93KspZZBf/I=
github.com/tetratelabs/wazero v1.9.0/go.mod h1:TSbcXCfFP0L2FGkRPxHphadXPjo1T6W+CseNNY7EkjM= github.com/tetratelabs/wazero v1.9.0/go.mod h1:TSbcXCfFP0L2FGkRPxHphadXPjo1T6W+CseNNY7EkjM=
github.com/zclconf/go-cty v1.16.2 h1:LAJSwc3v81IRBZyUVQDUdZ7hs3SYs9jv0eZJDWHD/70= github.com/zclconf/go-cty v1.16.2 h1:LAJSwc3v81IRBZyUVQDUdZ7hs3SYs9jv0eZJDWHD/70=
@ -73,14 +87,19 @@ go.opentelemetry.io/otel/sdk/metric v1.32.0 h1:rZvFnvmvawYb0alrYkjraqJq0Z4ZUJAiy
go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRYvOh1Gd+X99x6GHpCQ= go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRYvOh1Gd+X99x6GHpCQ=
go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM= go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM=
go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8= go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8=
golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs=
golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM= golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM=
golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20220408201424-a24fb2fb8a0f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY= golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY=
@ -91,6 +110,9 @@ google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ=
google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw= google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw=
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View file

@ -12,7 +12,7 @@ var (
MachinesColumns = []*schema.Column{ MachinesColumns = []*schema.Column{
{Name: "id", Type: field.TypeString}, {Name: "id", Type: field.TypeString},
{Name: "public_key", Type: field.TypeBytes}, {Name: "public_key", Type: field.TypeBytes},
{Name: "created_at", Type: field.TypeString, Default: "2025-02-23T06:06:02Z"}, {Name: "created_at", Type: field.TypeString, Default: "2025-03-01T18:10:35Z"},
} }
// MachinesTable holds the schema information for the "machines" table. // MachinesTable holds the schema information for the "machines" table.
MachinesTable = &schema.Table{ MachinesTable = &schema.Table{

View file

@ -5,6 +5,6 @@ package runtime
// The schema-stitching logic is generated in git.rgst.io/homelab/klefki/internal/db/ent/runtime.go // The schema-stitching logic is generated in git.rgst.io/homelab/klefki/internal/db/ent/runtime.go
const ( const (
Version = "v0.14.2" // Version of ent codegen. Version = "v0.14.3" // Version of ent codegen.
Sum = "h1:ywld/j2Rx4EmnIKs8eZ29cbFA1zpB+DA9TLL5l3rlq0=" // Sum of ent codegen. Sum = "h1:wokAV/kIlH9TeklJWGGS7AYJdVckr0DloWjIcO9iIIQ=" // Sum of ent codegen.
) )

View file

@ -29,8 +29,8 @@ import (
"git.rgst.io/homelab/klefki/internal/db/ent" "git.rgst.io/homelab/klefki/internal/db/ent"
) )
// getFingerprint returns a fingerprint of the key. // Fingerprint returns a fingerprint of the provided key.
func getFingerprint(pub ed25519.PublicKey) (string, error) { func Fingerprint(pub ed25519.PublicKey) (string, error) {
hasher := sha256.New() hasher := sha256.New()
if _, err := hasher.Write(pub); err != nil { if _, err := hasher.Write(pub); err != nil {
return "", fmt.Errorf("failed to hash provided public key: %w", err) return "", fmt.Errorf("failed to hash provided public key: %w", err)
@ -73,7 +73,7 @@ func (m *Machine) Fingerprint() (string, error) {
if m.fingerprint != "" { if m.fingerprint != "" {
return // NOOP if already set. return // NOOP if already set.
} }
m.fingerprint, err = getFingerprint(m.PublicKey) m.fingerprint, err = Fingerprint(m.PublicKey)
}) })
if err != nil { if err != nil {
return "", fmt.Errorf("failed to calculate fingerprint: %w", err) return "", fmt.Errorf("failed to calculate fingerprint: %w", err)
@ -121,3 +121,36 @@ func NewMachine() (*Machine, error) {
return &Machine{PublicKey: pub, PrivateKey: priv}, nil return &Machine{PublicKey: pub, PrivateKey: priv}, nil
} }
// DecodePrivateKey decodes a private key that was encoded by
// [Machine.EncodePrivateKey].
func DecodePrivateKey(data []byte) (ed25519.PrivateKey, error) {
b, _ := pem.Decode(data)
if b == nil {
return nil, fmt.Errorf("failed to parse private key as PEM encoded data")
}
if b.Type != "ED25519 PRIVATE KEY" {
return nil, fmt.Errorf("expected type \"ED25519 PRIVATE KEY\", got %s", b.Type)
}
k, err := x509.ParsePKCS8PrivateKey(b.Bytes)
if err != nil {
return nil, err
}
pk, ok := k.(ed25519.PrivateKey)
if !ok {
return nil, fmt.Errorf("expected ed25519.PrivateKey, got %T", k)
}
return pk, nil
}
// Verify takes the provided pubKey and determines if the provided
// signature was made by it, for the nonce. A nil error is success.
func Verify(pubKey ed25519.PublicKey, sig []byte, nonce string) error {
if ed25519.Verify(pubKey, []byte(nonce), sig) {
return nil
}
return fmt.Errorf("invalid signature")
}

50
internal/server/auth.go Normal file
View file

@ -0,0 +1,50 @@
// Copyright (C) 2025 klefki contributors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//
// SPDX-License-Identifier: AGPL-3.0
package server
import (
"context"
"crypto/ed25519"
pbgrpcv1 "git.rgst.io/homelab/klefki/internal/server/grpc/generated/go/rgst/klefki/v1"
)
// AuthChallenge is an authentication challenge.
type AuthChallenge struct {
// MachineID is the ID of the machine that sent this request
// (fingerprint).
MachineID string `json:"machine_id"`
// Signature is an ED25519 signature of the provided message.
Signature []byte `json:"signature"`
// Nonce is a randomly generated string that corresponds to the
// provided signature.
Nonce string `json:"nonce"`
}
// ValidateAuth determines if the auth presented is valid or not.
func (s *Server) ValidateAuth(ctx context.Context, req *pbgrpcv1.GetKeyRequest) bool {
// Get the public key for this node.
m, err := s.db.Machine.Get(ctx, req.GetMachineId())
if err != nil {
return false
}
return ed25519.Verify(m.PublicKey, []byte(req.GetNonce()), req.GetSignature())
}

View file

@ -23,9 +23,14 @@ const (
) )
type GetKeyRequest struct { type GetKeyRequest struct {
state protoimpl.MessageState `protogen:"opaque.v1"` state protoimpl.MessageState `protogen:"opaque.v1"`
unknownFields protoimpl.UnknownFields xxx_hidden_MachineId *string `protobuf:"bytes,1,opt,name=machine_id,json=machineId"`
sizeCache protoimpl.SizeCache xxx_hidden_Signature []byte `protobuf:"bytes,2,opt,name=signature"`
xxx_hidden_Nonce *string `protobuf:"bytes,3,opt,name=nonce"`
XXX_raceDetectHookData protoimpl.RaceDetectHookData
XXX_presence [1]uint32
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
} }
func (x *GetKeyRequest) Reset() { func (x *GetKeyRequest) Reset() {
@ -53,21 +58,117 @@ func (x *GetKeyRequest) ProtoReflect() protoreflect.Message {
return mi.MessageOf(x) return mi.MessageOf(x)
} }
func (x *GetKeyRequest) GetMachineId() string {
if x != nil {
if x.xxx_hidden_MachineId != nil {
return *x.xxx_hidden_MachineId
}
return ""
}
return ""
}
func (x *GetKeyRequest) GetSignature() []byte {
if x != nil {
return x.xxx_hidden_Signature
}
return nil
}
func (x *GetKeyRequest) GetNonce() string {
if x != nil {
if x.xxx_hidden_Nonce != nil {
return *x.xxx_hidden_Nonce
}
return ""
}
return ""
}
func (x *GetKeyRequest) SetMachineId(v string) {
x.xxx_hidden_MachineId = &v
protoimpl.X.SetPresent(&(x.XXX_presence[0]), 0, 3)
}
func (x *GetKeyRequest) SetSignature(v []byte) {
if v == nil {
v = []byte{}
}
x.xxx_hidden_Signature = v
protoimpl.X.SetPresent(&(x.XXX_presence[0]), 1, 3)
}
func (x *GetKeyRequest) SetNonce(v string) {
x.xxx_hidden_Nonce = &v
protoimpl.X.SetPresent(&(x.XXX_presence[0]), 2, 3)
}
func (x *GetKeyRequest) HasMachineId() bool {
if x == nil {
return false
}
return protoimpl.X.Present(&(x.XXX_presence[0]), 0)
}
func (x *GetKeyRequest) HasSignature() bool {
if x == nil {
return false
}
return protoimpl.X.Present(&(x.XXX_presence[0]), 1)
}
func (x *GetKeyRequest) HasNonce() bool {
if x == nil {
return false
}
return protoimpl.X.Present(&(x.XXX_presence[0]), 2)
}
func (x *GetKeyRequest) ClearMachineId() {
protoimpl.X.ClearPresent(&(x.XXX_presence[0]), 0)
x.xxx_hidden_MachineId = nil
}
func (x *GetKeyRequest) ClearSignature() {
protoimpl.X.ClearPresent(&(x.XXX_presence[0]), 1)
x.xxx_hidden_Signature = nil
}
func (x *GetKeyRequest) ClearNonce() {
protoimpl.X.ClearPresent(&(x.XXX_presence[0]), 2)
x.xxx_hidden_Nonce = nil
}
type GetKeyRequest_builder struct { type GetKeyRequest_builder struct {
_ [0]func() // Prevents comparability and use of unkeyed literals for the builder. _ [0]func() // Prevents comparability and use of unkeyed literals for the builder.
MachineId *string
Signature []byte
Nonce *string
} }
func (b0 GetKeyRequest_builder) Build() *GetKeyRequest { func (b0 GetKeyRequest_builder) Build() *GetKeyRequest {
m0 := &GetKeyRequest{} m0 := &GetKeyRequest{}
b, x := &b0, m0 b, x := &b0, m0
_, _ = b, x _, _ = b, x
if b.MachineId != nil {
protoimpl.X.SetPresentNonAtomic(&(x.XXX_presence[0]), 0, 3)
x.xxx_hidden_MachineId = b.MachineId
}
if b.Signature != nil {
protoimpl.X.SetPresentNonAtomic(&(x.XXX_presence[0]), 1, 3)
x.xxx_hidden_Signature = b.Signature
}
if b.Nonce != nil {
protoimpl.X.SetPresentNonAtomic(&(x.XXX_presence[0]), 2, 3)
x.xxx_hidden_Nonce = b.Nonce
}
return m0 return m0
} }
type GetKeyResponse struct { type GetKeyResponse struct {
state protoimpl.MessageState `protogen:"opaque.v1"` state protoimpl.MessageState `protogen:"opaque.v1"`
xxx_hidden_Key *string `protobuf:"bytes,1,opt,name=key"` xxx_hidden_Key []byte `protobuf:"bytes,1,opt,name=key"`
XXX_raceDetectHookData protoimpl.RaceDetectHookData XXX_raceDetectHookData protoimpl.RaceDetectHookData
XXX_presence [1]uint32 XXX_presence [1]uint32
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
@ -99,18 +200,18 @@ func (x *GetKeyResponse) ProtoReflect() protoreflect.Message {
return mi.MessageOf(x) return mi.MessageOf(x)
} }
func (x *GetKeyResponse) GetKey() string { func (x *GetKeyResponse) GetKey() []byte {
if x != nil { if x != nil {
if x.xxx_hidden_Key != nil { return x.xxx_hidden_Key
return *x.xxx_hidden_Key
}
return ""
} }
return "" return nil
} }
func (x *GetKeyResponse) SetKey(v string) { func (x *GetKeyResponse) SetKey(v []byte) {
x.xxx_hidden_Key = &v if v == nil {
v = []byte{}
}
x.xxx_hidden_Key = v
protoimpl.X.SetPresent(&(x.XXX_presence[0]), 0, 1) protoimpl.X.SetPresent(&(x.XXX_presence[0]), 0, 1)
} }
@ -129,7 +230,7 @@ func (x *GetKeyResponse) ClearKey() {
type GetKeyResponse_builder struct { type GetKeyResponse_builder struct {
_ [0]func() // Prevents comparability and use of unkeyed literals for the builder. _ [0]func() // Prevents comparability and use of unkeyed literals for the builder.
Key *string Key []byte
} }
func (b0 GetKeyResponse_builder) Build() *GetKeyResponse { func (b0 GetKeyResponse_builder) Build() *GetKeyResponse {
@ -151,20 +252,25 @@ var file_rgst_klefki_v1_kelfki_proto_rawDesc = string([]byte{
0x67, 0x73, 0x74, 0x2e, 0x6b, 0x6c, 0x65, 0x66, 0x6b, 0x69, 0x2e, 0x76, 0x31, 0x1a, 0x21, 0x67, 0x67, 0x73, 0x74, 0x2e, 0x6b, 0x6c, 0x65, 0x66, 0x6b, 0x69, 0x2e, 0x76, 0x31, 0x1a, 0x21, 0x67,
0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x67,
0x6f, 0x5f, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x6f, 0x5f, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x22, 0x0f, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x22, 0x62, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x22, 0x22, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x5f, 0x69, 0x64, 0x18,
0x6e, 0x73, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x49, 0x64,
0x52, 0x03, 0x6b, 0x65, 0x79, 0x32, 0x58, 0x0a, 0x0d, 0x4b, 0x6c, 0x65, 0x66, 0x6b, 0x69, 0x53, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x02, 0x20,
0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x47, 0x0a, 0x06, 0x47, 0x65, 0x74, 0x4b, 0x65, 0x79, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x14,
0x12, 0x1d, 0x2e, 0x72, 0x67, 0x73, 0x74, 0x2e, 0x6b, 0x6c, 0x65, 0x66, 0x6b, 0x69, 0x2e, 0x76, 0x0a, 0x05, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6e,
0x31, 0x2e, 0x47, 0x65, 0x74, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x6f, 0x6e, 0x63, 0x65, 0x22, 0x22, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x4b, 0x65, 0x79, 0x52, 0x65,
0x1e, 0x2e, 0x72, 0x67, 0x73, 0x74, 0x2e, 0x6b, 0x6c, 0x65, 0x66, 0x6b, 0x69, 0x2e, 0x76, 0x31, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20,
0x2e, 0x47, 0x65, 0x74, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x32, 0x58, 0x0a, 0x0d, 0x4b, 0x6c, 0x65, 0x66,
0x3f, 0x5a, 0x35, 0x67, 0x69, 0x74, 0x2e, 0x72, 0x67, 0x73, 0x74, 0x2e, 0x69, 0x6f, 0x2f, 0x69, 0x6b, 0x69, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x47, 0x0a, 0x06, 0x47, 0x65, 0x74,
0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x2f, 0x67, 0x65, 0x6e, 0x4b, 0x65, 0x79, 0x12, 0x1d, 0x2e, 0x72, 0x67, 0x73, 0x74, 0x2e, 0x6b, 0x6c, 0x65, 0x66, 0x6b,
0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x2f, 0x67, 0x6f, 0x2f, 0x72, 0x67, 0x73, 0x74, 0x2f, 0x6b, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65,
0x6c, 0x65, 0x66, 0x6b, 0x69, 0x2f, 0x76, 0x31, 0x92, 0x03, 0x05, 0xd2, 0x3e, 0x02, 0x10, 0x03, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x72, 0x67, 0x73, 0x74, 0x2e, 0x6b, 0x6c, 0x65, 0x66, 0x6b, 0x69,
0x62, 0x08, 0x65, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x70, 0xe8, 0x07, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x42, 0x3f, 0x5a, 0x35, 0x67, 0x69, 0x74, 0x2e, 0x72, 0x67, 0x73, 0x74, 0x2e, 0x69,
0x6f, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x2f,
0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x2f, 0x67, 0x6f, 0x2f, 0x72, 0x67, 0x73,
0x74, 0x2f, 0x6b, 0x6c, 0x65, 0x66, 0x6b, 0x69, 0x2f, 0x76, 0x31, 0x92, 0x03, 0x05, 0xd2, 0x3e,
0x02, 0x10, 0x03, 0x62, 0x08, 0x65, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x70, 0xe8, 0x07,
}) })
var file_rgst_klefki_v1_kelfki_proto_msgTypes = make([]protoimpl.MessageInfo, 2) var file_rgst_klefki_v1_kelfki_proto_msgTypes = make([]protoimpl.MessageInfo, 2)

View file

@ -7,10 +7,14 @@ import "google/protobuf/go_features.proto";
option features.(pb.go).api_level = API_OPAQUE; option features.(pb.go).api_level = API_OPAQUE;
option go_package = "git.rgst.io/internal/grpc/generated/go/rgst/klefki/v1"; option go_package = "git.rgst.io/internal/grpc/generated/go/rgst/klefki/v1";
message GetKeyRequest {} message GetKeyRequest {
string machine_id = 1;
bytes signature = 2;
string nonce = 3;
}
message GetKeyResponse { message GetKeyResponse {
string key = 1; bytes key = 1;
} }
service KlefkiService { service KlefkiService {

View file

@ -19,23 +19,52 @@
package server package server
import ( import (
"bytes"
"context" "context"
"fmt" "fmt"
"io"
"net" "net"
"strings"
"git.rgst.io/homelab/klefki/internal/db"
"git.rgst.io/homelab/klefki/internal/db/ent"
"git.rgst.io/homelab/klefki/internal/machines"
pbgrpcv1 "git.rgst.io/homelab/klefki/internal/server/grpc/generated/go/rgst/klefki/v1" pbgrpcv1 "git.rgst.io/homelab/klefki/internal/server/grpc/generated/go/rgst/klefki/v1"
"git.rgst.io/homelab/sigtool/v3/sign"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/reflection" "google.golang.org/grpc/reflection"
) )
// nopWriteCloser is a no-op [io.WriteCloser]
type nopWriteCloser struct {
io.Writer
}
// Close implements [io.Closer]
func (nwc nopWriteCloser) Close() error {
return nil
}
// newNopWriteCloser creates a new nopWriteCloser
func newNopWriteCloser(w io.Writer) *nopWriteCloser {
return &nopWriteCloser{w}
}
// Server is a Klefki gRPC server // Server is a Klefki gRPC server
type Server struct { type Server struct {
gs *grpc.Server gs *grpc.Server
db *ent.Client
pbgrpcv1.UnimplementedKlefkiServiceServer pbgrpcv1.UnimplementedKlefkiServiceServer
} }
// Run starts the server // Run starts the server
func (s *Server) Run(_ context.Context) error { func (s *Server) Run(ctx context.Context) error {
var err error
s.db, err = db.New(ctx)
if err != nil {
return fmt.Errorf("failed to open DB: %w", err)
}
s.gs = grpc.NewServer() s.gs = grpc.NewServer()
pbgrpcv1.RegisterKlefkiServiceServer(s.gs, s) pbgrpcv1.RegisterKlefkiServiceServer(s.gs, s)
reflection.Register(s.gs) reflection.Register(s.gs)
@ -50,9 +79,42 @@ func (s *Server) Run(_ context.Context) error {
} }
// GetKey implements the GetKey request // GetKey implements the GetKey request
func (s *Server) GetKey(_ context.Context, _ *pbgrpcv1.GetKeyRequest) (*pbgrpcv1.GetKeyResponse, error) { func (s *Server) GetKey(ctx context.Context, req *pbgrpcv1.GetKeyRequest) (*pbgrpcv1.GetKeyResponse, error) {
resp := &pbgrpcv1.GetKeyResponse{} resp := &pbgrpcv1.GetKeyResponse{}
resp.SetKey("hello-world")
nonce := req.GetNonce()
sig := req.GetSignature()
machine, err := s.db.Machine.Get(ctx, req.GetMachineId())
if err != nil {
return nil, err
}
if err := machines.Verify(machine.PublicKey, sig, nonce); err != nil {
return nil, err
}
spubk, err := sign.PublicKeyFromBytes(machine.PublicKey)
if err != nil {
return nil, fmt.Errorf("failed to create pub key for encryption: %w", err)
}
enc, err := sign.NewEncryptor(nil, 1024)
if err != nil {
return nil, fmt.Errorf("failed to create encryptor instance: %w", err)
}
if err := enc.AddRecipient(spubk); err != nil {
return nil, fmt.Errorf("failed to add instance public key to encryptor: %w", err)
}
// TODO(jaredallard): Wait for input here.
var buf bytes.Buffer
if err := enc.Encrypt(strings.NewReader("hello world"), newNopWriteCloser(&buf)); err != nil {
return nil, fmt.Errorf("failed to encrypt passphrase: %w", err)
}
resp.SetKey(buf.Bytes())
return resp, nil return resp, nil
} }
@ -63,7 +125,6 @@ func (s *Server) Close(_ context.Context) error {
} }
fmt.Println("shutting down server") fmt.Println("shutting down server")
s.gs.GracefulStop() s.gs.GracefulStop()
return nil return s.db.Close()
} }