Migrate mdns functions from tpm helpers to this repo

because tpm has nothing to do with mdns.

TODO: Remove the functions from tpm helpers and bump the module here

Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me>
This commit is contained in:
Dimitris Karakasilis 2024-01-22 19:48:12 +02:00
parent bf59ecd475
commit 311b8adda0
7 changed files with 154 additions and 31 deletions

View File

@ -16,6 +16,9 @@ import (
"github.com/mudler/yip/pkg/utils"
)
// Because of how go-pluggable works, we can't just print to stdout
const LOGFILE = "/tmp/kcrypt-challenger-client.log"
var errPartNotFound error = fmt.Errorf("pass for partition not found")
var errBadCertificate error = fmt.Errorf("unknown certificate")
@ -30,6 +33,10 @@ func NewClient() (*Client, error) {
// echo '{ "data": "{ \\"label\\": \\"LABEL\\" }"}' | sudo -E WSS_SERVER="http://localhost:8082/challenge" ./challenger "discovery.password"
func (c *Client) Start() error {
if err := os.RemoveAll(LOGFILE); err != nil { // Start fresh
return fmt.Errorf("removing the logfile: %w", err)
}
factory := pluggable.NewPluginFactory()
// Input: bus.EventInstallPayload
@ -59,7 +66,7 @@ func (c *Client) Start() error {
return factory.Run(pluggable.EventType(os.Args[1]), os.Stdin, os.Stdout)
}
func (c *Client) generatePass(postEndpoint string, p *block.Partition) error {
func (c *Client) generatePass(postEndpoint string, headers map[string]string, p *block.Partition) error {
rand := utils.RandomString(32)
pass, err := tpm.EncryptBlob([]byte(rand))
@ -75,6 +82,10 @@ func (c *Client) generatePass(postEndpoint string, p *block.Partition) error {
tpm.WithAdditionalHeader("name", p.Name),
tpm.WithAdditionalHeader("uuid", p.UUID),
}
for k, v := range headers {
opts = append(opts, tpm.WithAdditionalHeader(k, v))
}
conn, err := tpm.Connection(postEndpoint, opts...)
if err != nil {
return err
@ -84,20 +95,27 @@ func (c *Client) generatePass(postEndpoint string, p *block.Partition) error {
}
func (c *Client) waitPass(p *block.Partition, attempts int) (pass string, err error) {
// IF we don't have any server configured, just do local
if c.Config.Kcrypt.Challenger.Server == "" {
additionalHeaders := map[string]string{}
serverURL := c.Config.Kcrypt.Challenger.Server
// If we don't have any server configured, just do local
if serverURL == "" {
return localPass(c.Config)
}
challengeEndpoint := fmt.Sprintf("%s/getPass", c.Config.Kcrypt.Challenger.Server)
postEndpoint := fmt.Sprintf("%s/postPass", c.Config.Kcrypt.Challenger.Server)
if c.Config.Kcrypt.Challenger.MDNS {
serverURL, additionalHeaders, err = queryMDNS(serverURL)
}
getEndpoint := fmt.Sprintf("%s/getPass", serverURL)
postEndpoint := fmt.Sprintf("%s/postPass", serverURL)
for tries := 0; tries < attempts; tries++ {
var generated bool
pass, generated, err = getPass(challengeEndpoint, c.Config.Kcrypt.Challenger.Certificate, p)
pass, generated, err = getPass(getEndpoint, additionalHeaders, c.Config.Kcrypt.Challenger.Certificate, p)
if err == errPartNotFound {
// IF server doesn't have a pass for us, then we generate one and we set it
err = c.generatePass(postEndpoint, p)
err = c.generatePass(postEndpoint, additionalHeaders, p)
if err != nil {
return
}
@ -118,7 +136,7 @@ func (c *Client) waitPass(p *block.Partition, attempts int) (pass string, err er
return
}
fmt.Printf("Failed with error: %s . Will retry.\n", err.Error())
logToFile("Failed with error: %s . Will retry.\n", err.Error())
time.Sleep(1 * time.Second) // network errors? retry
}
@ -145,3 +163,14 @@ func (c *Client) decryptPassphrase(pass string) (string, error) {
return string(passBytes), err
}
func logToFile(format string, a ...any) {
s := fmt.Sprintf(format, a...)
file, err := os.OpenFile(LOGFILE, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
panic(err)
}
defer file.Close()
file.WriteString(s)
}

View File

@ -13,11 +13,10 @@ type Client struct {
type Config struct {
Kcrypt struct {
Challenger struct {
Server string `yaml:"challenger_server,omitempty"`
// Non-volatile index memory: where we store the encrypted passphrase (offline mode)
NVIndex string `yaml:"nv_index,omitempty"`
// Certificate index: this is where the rsa pair that decrypts the passphrase lives
CIndex string `yaml:"c_index,omitempty"`
MDNS bool `yaml:"mdns,omitempty"`
Server string `yaml:"challenger_server,omitempty"`
NVIndex string `yaml:"nv_index,omitempty"` // Non-volatile index memory: where we store the encrypted passphrase (offline mode)
CIndex string `yaml:"c_index,omitempty"` // Certificate index: this is where the rsa pair that decrypts the passphrase lives
TPMDevice string `yaml:"tpm_device,omitempty"`
Certificate string `yaml:"certificate,omitempty"`
}

View File

@ -16,13 +16,19 @@ import (
const DefaultNVIndex = "0x1500000"
func getPass(server, certificate string, partition *block.Partition) (string, bool, error) {
msg, err := tpm.Get(server,
func getPass(server string, headers map[string]string, certificate string, partition *block.Partition) (string, bool, error) {
opts := []tpm.Option{
tpm.WithCAs([]byte(certificate)),
tpm.AppendCustomCAToSystemCA,
tpm.WithAdditionalHeader("label", partition.FilesystemLabel),
tpm.WithAdditionalHeader("name", partition.Name),
tpm.WithAdditionalHeader("uuid", partition.UUID))
tpm.WithAdditionalHeader("uuid", partition.UUID),
}
for k, v := range headers {
opts = append(opts, tpm.WithAdditionalHeader(k, v))
}
msg, err := tpm.Get(server, opts...)
if err != nil {
return "", false, err
}

View File

@ -0,0 +1,85 @@
package client
import (
"fmt"
"net/url"
"strconv"
"strings"
"time"
"github.com/hashicorp/mdns"
)
const (
MDNSServiceType = "_kcrypt._tcp"
MDNSTimeout = 15 * time.Second
)
// queryMDNS will make an mdns query on local network to find a kcrypt challenger server
// instance. If none is found, the original URL is returned and no additional headers.
// If a response is received, the IP address and port from the response will be returned// and an additional "Host" header pointing to the original host.
func queryMDNS(originalURL string) (string, map[string]string, error) {
additionalHeaders := map[string]string{}
var err error
parsedURL, err := url.Parse(originalURL)
if err != nil {
return originalURL, additionalHeaders, fmt.Errorf("parsing the original host: %w", err)
}
host := parsedURL.Host
if !strings.HasSuffix(host, ".local") { // sanity check
return "", additionalHeaders, fmt.Errorf("domain should end in \".local\" when using mdns")
}
mdnsIP, mdnsPort := discoverMDNSServer(host)
if mdnsIP == "" { // no reply
logToFile("no reply from mdns\n")
return originalURL, additionalHeaders, nil
}
additionalHeaders["Host"] = parsedURL.Host
newURL := strings.ReplaceAll(originalURL, host, mdnsIP)
// Remove any port in the original url
if port := parsedURL.Port(); port != "" {
newURL = strings.ReplaceAll(newURL, port, "")
}
// Add any possible port from the mdns response
if mdnsPort != "" {
newURL = strings.ReplaceAll(newURL, mdnsIP, fmt.Sprintf("%s:%s", mdnsIP, mdnsPort))
}
return newURL, additionalHeaders, nil
}
// discoverMDNSServer performs an mDNS query to discover any running kcrypt challenger
// servers on the same network that matches the given hostname.
// If a response if received, the IP address and the Port from the response are returned.
func discoverMDNSServer(hostname string) (string, string) {
// Make a channel for results and start listening
entriesCh := make(chan *mdns.ServiceEntry, 4)
defer close(entriesCh)
logToFile("Will now wait for some mdns server to respond\n")
// Start the lookup. It will block until we read from the chan.
mdns.Lookup(MDNSServiceType, entriesCh)
expectedHost := hostname + "." // FQDN
// Wait until a matching server is found or we reach a timeout
for {
select {
case entry := <-entriesCh:
logToFile("mdns response received\n")
if entry.Host == expectedHost {
logToFile("%s matches %s\n", entry.Host, expectedHost)
return entry.AddrV4.String(), strconv.Itoa(entry.Port) // TODO: v6?
} else {
logToFile("%s didn't match %s\n", entry.Host, expectedHost)
}
case <-time.After(MDNSTimeout):
logToFile("timed out waiting for mdns\n")
return "", ""
}
}
}

4
go.mod
View File

@ -6,10 +6,11 @@ require (
github.com/go-logr/logr v1.2.4
github.com/google/uuid v1.3.0
github.com/gorilla/websocket v1.5.0
github.com/hashicorp/mdns v1.0.5
github.com/jaypipes/ghw v0.11.0
github.com/kairos-io/kairos-sdk v0.0.15
github.com/kairos-io/kcrypt v0.7.0
github.com/kairos-io/tpm-helpers v0.0.0-20231220125202-70a0b64e5111
github.com/kairos-io/tpm-helpers v0.0.0-20240123063624-f7a3fcc66199
github.com/mudler/go-pluggable v0.0.0-20230126220627-7710299a0ae5
github.com/mudler/go-processmanager v0.0.0-20220724164624-c45b5c61312d
github.com/mudler/yip v1.3.0
@ -85,7 +86,6 @@ require (
github.com/gookit/color v1.5.3 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/mdns v1.0.5 // indirect
github.com/huandu/xstrings v1.3.3 // indirect
github.com/imdario/mergo v0.3.15 // indirect
github.com/ipfs/go-log v1.0.5 // indirect

4
go.sum
View File

@ -415,8 +415,8 @@ github.com/kairos-io/kairos-sdk v0.0.15 h1:1hcnRfKlBzDWcZ8z7UrUqJ2v6GafCHZknPqm9
github.com/kairos-io/kairos-sdk v0.0.15/go.mod h1:Ew3NKFuXByu3Y3yGu8Q92M3oMqsXrg2VilouubdhYqA=
github.com/kairos-io/kcrypt v0.7.0 h1:ESmCBIFbBBv7mJf0/f6ugqwSvz63M5oP9sUIdHiDlLc=
github.com/kairos-io/kcrypt v0.7.0/go.mod h1:a9eI+vPVIQHPRtqEV/O/yIfDOdMWd9epVrq1p94gccM=
github.com/kairos-io/tpm-helpers v0.0.0-20231220125202-70a0b64e5111 h1:iEBUZdneuS3YfRunKmRpxfCe4XBxeSiBcIXRmNcUzrw=
github.com/kairos-io/tpm-helpers v0.0.0-20231220125202-70a0b64e5111/go.mod h1:mxKPLc7DQu9bcL8hv8A5PqTSH/rWm0M7SGolwYRYJq4=
github.com/kairos-io/tpm-helpers v0.0.0-20240123063624-f7a3fcc66199 h1:eXiZNiQfZDelYfTF733IxDOKGAKJqn0fF0kFY10QreU=
github.com/kairos-io/tpm-helpers v0.0.0-20240123063624-f7a3fcc66199/go.mod h1:6YGebKVrPoJGBd9QE+x4zyuo3vPw1y33iQkNChjlBo8=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=

View File

@ -17,19 +17,23 @@ k3d cluster create kcrypt -p '30000:30000@server:0'
```
helm repo add kairos https://kairos-io.github.io/helm-charts
helm install kairos-crd kairos/kairos-crds
helm install kairos-challenger kairos/kairos-challenger
```
- Edit the challenger service `kairos-challenger-escrow-service` and change it to NodePort (or do it through the helm chart when installing)
Create the following 'kcrypt-challenger-values.yaml` file:
```
ports:
- name: wss
```yaml
service:
challenger:
type: "NodePort"
port: 8082
protocol: TCP
targetPort: 8082
nodePort: 30000
type: NodePort
```
and deploy the challenger server with it:
```bash
helm install -f kcrypt-challenger-values.yaml kairos-challenger kairos/kairos-challenger
```
- Add the sealedvolume and secret for the tpm chip:
@ -52,7 +56,7 @@ metadata:
spec:
TPMHash: "5640e37f4016da16b841a93880dcc44886904392fa3c86681087b77db5afedbe"
partitions:
- label: "persistent"
- label: COS_PERSISTENT
secret:
name: example-host-tpm-secret
path: pass
@ -62,7 +66,7 @@ spec:
- Start the [simple-mdns-server](https://github.com/kairos-io/simple-mdns-server)
```
go run . --port 30000 --interfaceName enp121s0 --serviceType _kcrypt._tcp
go run . --port 30000 --interfaceName enp121s0 --serviceType _kcrypt._tcp --hostName mychallenger.local
```
@ -86,7 +90,7 @@ install:
# Kcrypt configuration block
kcrypt:
challenger:
challenger_server: "http://doesnt-matter-the-name.local"
challenger_server: "http://mychallenger.local"
```
- Install: