mirror of
https://github.com/kairos-io/kcrypt-challenger.git
synced 2025-09-05 16:52:19 +00:00
Compare commits
5 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
f8e7a0df87 | ||
|
968ff53267 | ||
|
a95436bf16 | ||
|
dfe29aa24f | ||
|
db2b6758de |
@@ -9,6 +9,7 @@ import (
|
|||||||
|
|
||||||
"github.com/jaypipes/ghw/pkg/block"
|
"github.com/jaypipes/ghw/pkg/block"
|
||||||
"github.com/kairos-io/kairos-challenger/pkg/constants"
|
"github.com/kairos-io/kairos-challenger/pkg/constants"
|
||||||
|
"github.com/kairos-io/kairos-challenger/pkg/payload"
|
||||||
"github.com/kairos-io/kcrypt/pkg/bus"
|
"github.com/kairos-io/kcrypt/pkg/bus"
|
||||||
"github.com/kairos-io/tpm-helpers"
|
"github.com/kairos-io/tpm-helpers"
|
||||||
"github.com/mudler/go-pluggable"
|
"github.com/mudler/go-pluggable"
|
||||||
@@ -57,21 +58,12 @@ func (c *Client) Start() error {
|
|||||||
return factory.Run(pluggable.EventType(os.Args[1]), os.Stdin, os.Stdout)
|
return factory.Run(pluggable.EventType(os.Args[1]), os.Stdin, os.Stdout)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) waitPass(p *block.Partition, attempts int) (pass string, err error) {
|
func (c *Client) generatePass(postEndpoint string, p *block.Partition) error {
|
||||||
// IF we don't have any server configured, just do local
|
|
||||||
if c.Config.Kcrypt.Challenger.Server == "" {
|
|
||||||
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 server doesn't have a pass for us, then we generate one and we set it
|
|
||||||
if _, _, err := getPass(challengeEndpoint, p); err == errPartNotFound {
|
|
||||||
rand := utils.RandomString(32)
|
rand := utils.RandomString(32)
|
||||||
pass, err := tpm.EncryptBlob([]byte(rand))
|
pass, err := tpm.EncryptBlob([]byte(rand))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return err
|
||||||
}
|
}
|
||||||
bpass := base64.RawURLEncoding.EncodeToString(pass)
|
bpass := base64.RawURLEncoding.EncodeToString(pass)
|
||||||
|
|
||||||
@@ -82,17 +74,34 @@ func (c *Client) waitPass(p *block.Partition, attempts int) (pass string, err er
|
|||||||
}
|
}
|
||||||
conn, err := tpm.Connection(postEndpoint, opts...)
|
conn, err := tpm.Connection(postEndpoint, opts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return err
|
||||||
}
|
}
|
||||||
err = conn.WriteJSON(map[string]string{"passphrase": bpass, constants.GeneratedByKey: constants.TPMSecret})
|
|
||||||
if err != nil {
|
return conn.WriteJSON(payload.Data{Passphrase: bpass, GeneratedBy: constants.TPMSecret})
|
||||||
return rand, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 == "" {
|
||||||
|
return localPass(c.Config)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
challengeEndpoint := fmt.Sprintf("%s/getPass", c.Config.Kcrypt.Challenger.Server)
|
||||||
|
postEndpoint := fmt.Sprintf("%s/postPass", c.Config.Kcrypt.Challenger.Server)
|
||||||
|
|
||||||
for tries := 0; tries < attempts; tries++ {
|
for tries := 0; tries < attempts; tries++ {
|
||||||
var generated bool
|
var generated bool
|
||||||
pass, generated, err = getPass(challengeEndpoint, p)
|
pass, generated, err = getPass(challengeEndpoint, 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)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Attempt to fetch again - validate that the server has it now
|
||||||
|
tries = 0
|
||||||
|
continue
|
||||||
|
}
|
||||||
if generated { // passphrase is encrypted
|
if generated { // passphrase is encrypted
|
||||||
return c.decryptPassphrase(pass)
|
return c.decryptPassphrase(pass)
|
||||||
}
|
}
|
||||||
|
@@ -3,8 +3,10 @@ package client
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/kairos-io/kairos-challenger/pkg/constants"
|
"github.com/kairos-io/kairos-challenger/pkg/constants"
|
||||||
|
"github.com/kairos-io/kairos-challenger/pkg/payload"
|
||||||
|
|
||||||
"github.com/jaypipes/ghw/pkg/block"
|
"github.com/jaypipes/ghw/pkg/block"
|
||||||
"github.com/kairos-io/tpm-helpers"
|
"github.com/kairos-io/tpm-helpers"
|
||||||
@@ -22,16 +24,21 @@ func getPass(server string, partition *block.Partition) (string, bool, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", false, err
|
return "", false, err
|
||||||
}
|
}
|
||||||
result := map[string]interface{}{}
|
result := payload.Data{}
|
||||||
err = json.Unmarshal(msg, &result)
|
err = json.Unmarshal(msg, &result)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", false, errors.Wrap(err, string(msg))
|
return "", false, errors.Wrap(err, string(msg))
|
||||||
}
|
}
|
||||||
generatedBy, generated := result[constants.GeneratedByKey]
|
|
||||||
p, ok := result["passphrase"]
|
if result.HasPassphrase() {
|
||||||
if ok {
|
return fmt.Sprint(result.Passphrase), result.HasBeenGenerated() && result.GeneratedBy == constants.TPMSecret, nil
|
||||||
return fmt.Sprint(p), generated && generatedBy == constants.TPMSecret, nil
|
} else if result.HasError() {
|
||||||
|
if strings.Contains(result.Error, "No secret found for") {
|
||||||
|
return "", false, errPartNotFound
|
||||||
}
|
}
|
||||||
|
return "", false, fmt.Errorf(result.Error)
|
||||||
|
}
|
||||||
|
|
||||||
return "", false, errPartNotFound
|
return "", false, errPartNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -4,12 +4,15 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
keyserverv1alpha1 "github.com/kairos-io/kairos-challenger/api/v1alpha1"
|
keyserverv1alpha1 "github.com/kairos-io/kairos-challenger/api/v1alpha1"
|
||||||
"github.com/kairos-io/kairos-challenger/pkg/constants"
|
"github.com/kairos-io/kairos-challenger/pkg/constants"
|
||||||
|
"github.com/kairos-io/kairos-challenger/pkg/payload"
|
||||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||||
|
|
||||||
"github.com/kairos-io/kairos-challenger/controllers"
|
"github.com/kairos-io/kairos-challenger/controllers"
|
||||||
@@ -45,6 +48,24 @@ var upgrader = websocket.Upgrader{
|
|||||||
WriteBufferSize: 1024,
|
WriteBufferSize: 1024,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func cleanKubeName(s string) (d string) {
|
||||||
|
d = strings.ReplaceAll(s, "_", "-")
|
||||||
|
d = strings.ToLower(d)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s SealedVolumeData) DefaultSecret() (string, string) {
|
||||||
|
secretName := fmt.Sprintf("%s-%s", s.VolumeName, s.PartitionLabel)
|
||||||
|
secretPath := "passphrase"
|
||||||
|
if s.SecretName != "" {
|
||||||
|
secretName = s.SecretName
|
||||||
|
}
|
||||||
|
if s.SecretPath != "" {
|
||||||
|
secretPath = s.SecretPath
|
||||||
|
}
|
||||||
|
return cleanKubeName(secretName), cleanKubeName(secretPath)
|
||||||
|
}
|
||||||
|
|
||||||
func writeRead(conn *websocket.Conn, input []byte) ([]byte, error) {
|
func writeRead(conn *websocket.Conn, input []byte) ([]byte, error) {
|
||||||
writer, err := conn.NextWriter(websocket.BinaryMessage)
|
writer, err := conn.NextWriter(websocket.BinaryMessage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -86,14 +107,26 @@ func Start(ctx context.Context, kclient *kubernetes.Clientset, reconciler *contr
|
|||||||
|
|
||||||
m := http.NewServeMux()
|
m := http.NewServeMux()
|
||||||
|
|
||||||
|
errorMessage := func(writer io.WriteCloser, errMsg string) {
|
||||||
|
err := json.NewEncoder(writer).Encode(payload.Data{Error: errMsg})
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("error encoding the response to json", err.Error())
|
||||||
|
}
|
||||||
|
fmt.Println(errMsg)
|
||||||
|
}
|
||||||
|
|
||||||
m.HandleFunc("/postPass", func(w http.ResponseWriter, r *http.Request) {
|
m.HandleFunc("/postPass", func(w http.ResponseWriter, r *http.Request) {
|
||||||
conn, _ := upgrader.Upgrade(w, r, nil) // error ignored for sake of simplicity
|
conn, _ := upgrader.Upgrade(w, r, nil) // error ignored for sake of simplicity
|
||||||
for {
|
for {
|
||||||
|
|
||||||
|
fmt.Println("Receiving passphrase")
|
||||||
if err := tpm.AuthRequest(r, conn); err != nil {
|
if err := tpm.AuthRequest(r, conn); err != nil {
|
||||||
fmt.Println("error", err.Error())
|
fmt.Println("error", err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
fmt.Println("[Receiving passphrase] auth succeeded")
|
||||||
|
|
||||||
token := r.Header.Get("Authorization")
|
token := r.Header.Get("Authorization")
|
||||||
|
|
||||||
hashEncoded, err := getPubHash(token)
|
hashEncoded, err := getPubHash(token)
|
||||||
@@ -101,11 +134,12 @@ func Start(ctx context.Context, kclient *kubernetes.Clientset, reconciler *contr
|
|||||||
fmt.Println("error decoding pubhash", err.Error())
|
fmt.Println("error decoding pubhash", err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
fmt.Println("[Receiving passphrase] pubhash", hashEncoded)
|
||||||
|
|
||||||
label := r.Header.Get("label")
|
label := r.Header.Get("label")
|
||||||
name := r.Header.Get("name")
|
name := r.Header.Get("name")
|
||||||
uuid := r.Header.Get("uuid")
|
uuid := r.Header.Get("uuid")
|
||||||
v := map[string]string{}
|
v := &payload.Data{}
|
||||||
|
|
||||||
volumeList := &keyserverv1alpha1.SealedVolumeList{}
|
volumeList := &keyserverv1alpha1.SealedVolumeList{}
|
||||||
if err := reconciler.List(ctx, volumeList, &client.ListOptions{Namespace: namespace}); err != nil {
|
if err := reconciler.List(ctx, volumeList, &client.ListOptions{Namespace: namespace}); err != nil {
|
||||||
@@ -127,21 +161,13 @@ func Start(ctx context.Context, kclient *kubernetes.Clientset, reconciler *contr
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := conn.ReadJSON(&v); err != nil {
|
if err := conn.ReadJSON(v); err != nil {
|
||||||
fmt.Println("error", err.Error())
|
fmt.Println("error", err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
pass, ok := v["passphrase"]
|
if v.HasPassphrase() && !v.HasError() {
|
||||||
if ok {
|
secretName, secretPath := sealedVolumeData.DefaultSecret()
|
||||||
secretName := fmt.Sprintf("%s-%s", sealedVolumeData.VolumeName, sealedVolumeData.PartitionLabel)
|
|
||||||
secretPath := "passphrase"
|
|
||||||
if sealedVolumeData.SecretName != "" {
|
|
||||||
secretName = sealedVolumeData.SecretName
|
|
||||||
}
|
|
||||||
if sealedVolumeData.SecretPath != "" {
|
|
||||||
secretPath = sealedVolumeData.SecretPath
|
|
||||||
}
|
|
||||||
_, err := kclient.CoreV1().Secrets(namespace).Get(ctx, secretName, v1.GetOptions{})
|
_, err := kclient.CoreV1().Secrets(namespace).Get(ctx, secretName, v1.GetOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !apierrors.IsNotFound(err) {
|
if !apierrors.IsNotFound(err) {
|
||||||
@@ -158,15 +184,15 @@ func Start(ctx context.Context, kclient *kubernetes.Clientset, reconciler *contr
|
|||||||
Name: secretName,
|
Name: secretName,
|
||||||
Namespace: namespace,
|
Namespace: namespace,
|
||||||
},
|
},
|
||||||
Data: map[string][]byte{
|
StringData: map[string]string{
|
||||||
secretPath: []byte(pass),
|
secretPath: v.Passphrase,
|
||||||
constants.GeneratedByKey: []byte(v[constants.GeneratedByKey]),
|
constants.GeneratedByKey: v.GeneratedBy,
|
||||||
},
|
},
|
||||||
Type: "Opaque",
|
Type: "Opaque",
|
||||||
}
|
}
|
||||||
_, err := kclient.CoreV1().Secrets(namespace).Create(ctx, &secret, v1.CreateOptions{})
|
_, err := kclient.CoreV1().Secrets(namespace).Create(ctx, &secret, v1.CreateOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("failed during secret creation")
|
fmt.Println("failed during secret creation:", err.Error())
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
fmt.Println("Posted for already existing secret - ignoring")
|
fmt.Println("Posted for already existing secret - ignoring")
|
||||||
@@ -213,20 +239,15 @@ func Start(ctx context.Context, kclient *kubernetes.Clientset, reconciler *contr
|
|||||||
}, volumeList)
|
}, volumeList)
|
||||||
|
|
||||||
if sealedVolumeData == nil {
|
if sealedVolumeData == nil {
|
||||||
fmt.Println("No TPM Hash found for", hashEncoded)
|
writer, _ := conn.NextWriter(websocket.BinaryMessage)
|
||||||
|
errorMessage(writer, fmt.Sprintf("Invalid hash: %s", hashEncoded))
|
||||||
conn.Close()
|
conn.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
writer, _ := conn.NextWriter(websocket.BinaryMessage)
|
writer, _ := conn.NextWriter(websocket.BinaryMessage)
|
||||||
if !sealedVolumeData.Quarantined {
|
if !sealedVolumeData.Quarantined {
|
||||||
secretName := fmt.Sprintf("%s-%s", sealedVolumeData.VolumeName, sealedVolumeData.PartitionLabel)
|
secretName, secretPath := sealedVolumeData.DefaultSecret()
|
||||||
secretPath := "passphrase"
|
|
||||||
if sealedVolumeData.SecretName != "" {
|
|
||||||
secretName = sealedVolumeData.SecretName
|
|
||||||
}
|
|
||||||
if sealedVolumeData.SecretPath != "" {
|
|
||||||
secretPath = sealedVolumeData.SecretPath
|
|
||||||
}
|
|
||||||
|
|
||||||
// 1. The admin sets a specific cleartext password from Kube manager
|
// 1. The admin sets a specific cleartext password from Kube manager
|
||||||
// SealedVolume -> with a secret .
|
// SealedVolume -> with a secret .
|
||||||
@@ -236,12 +257,10 @@ func Start(ctx context.Context, kclient *kubernetes.Clientset, reconciler *contr
|
|||||||
secret, err := kclient.CoreV1().Secrets(namespace).Get(ctx, secretName, v1.GetOptions{})
|
secret, err := kclient.CoreV1().Secrets(namespace).Get(ctx, secretName, v1.GetOptions{})
|
||||||
if err == nil {
|
if err == nil {
|
||||||
passphrase := secret.Data[secretPath]
|
passphrase := secret.Data[secretPath]
|
||||||
generatedBy, generated := secret.Data[constants.GeneratedByKey]
|
generatedBy := secret.Data[constants.GeneratedByKey]
|
||||||
result := map[string]string{"passphrase": string(passphrase)}
|
|
||||||
if generated {
|
p := payload.Data{Passphrase: string(passphrase), GeneratedBy: string(generatedBy)}
|
||||||
result[constants.GeneratedByKey] = string(generatedBy)
|
err = json.NewEncoder(writer).Encode(p)
|
||||||
}
|
|
||||||
err = json.NewEncoder(writer).Encode(result)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("error encoding the passphrase to json", err.Error(), string(passphrase))
|
fmt.Println("error encoding the passphrase to json", err.Error(), string(passphrase))
|
||||||
}
|
}
|
||||||
@@ -255,9 +274,11 @@ func Start(ctx context.Context, kclient *kubernetes.Clientset, reconciler *contr
|
|||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
|
} else {
|
||||||
|
errorMessage(writer, fmt.Sprintf("No secret found for %s and %s", hashEncoded, sealedVolumeData.PartitionLabel))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
fmt.Println("error getting the secret", err.Error())
|
errorMessage(writer, fmt.Sprintf("quarantined: %s", sealedVolumeData.PartitionLabel))
|
||||||
if err = conn.Close(); err != nil {
|
if err = conn.Close(); err != nil {
|
||||||
fmt.Println("error closing the connection", err.Error())
|
fmt.Println("error closing the connection", err.Error())
|
||||||
return
|
return
|
||||||
@@ -290,12 +311,19 @@ func findVolumeFor(requestData PassphraseRequestData, volumeList *keyserverv1alp
|
|||||||
deviceNameMatches := requestData.DeviceName != "" && p.DeviceName == requestData.DeviceName
|
deviceNameMatches := requestData.DeviceName != "" && p.DeviceName == requestData.DeviceName
|
||||||
uuidMatches := requestData.UUID != "" && p.UUID == requestData.UUID
|
uuidMatches := requestData.UUID != "" && p.UUID == requestData.UUID
|
||||||
labelMatches := requestData.Label != "" && p.Label == requestData.Label
|
labelMatches := requestData.Label != "" && p.Label == requestData.Label
|
||||||
|
secretName := ""
|
||||||
|
if p.Secret != nil && p.Secret.Name != "" {
|
||||||
|
secretName = p.Secret.Name
|
||||||
|
}
|
||||||
|
secretPath := ""
|
||||||
|
if p.Secret != nil && p.Secret.Path != "" {
|
||||||
|
secretPath = p.Secret.Path
|
||||||
|
}
|
||||||
if labelMatches || uuidMatches || deviceNameMatches {
|
if labelMatches || uuidMatches || deviceNameMatches {
|
||||||
return &SealedVolumeData{
|
return &SealedVolumeData{
|
||||||
Quarantined: v.Spec.Quarantined,
|
Quarantined: v.Spec.Quarantined,
|
||||||
SecretName: p.Secret.Name,
|
SecretName: secretName,
|
||||||
SecretPath: p.Secret.Path,
|
SecretPath: secretPath,
|
||||||
VolumeName: v.Name,
|
VolumeName: v.Name,
|
||||||
PartitionLabel: p.Label,
|
PartitionLabel: p.Label,
|
||||||
}
|
}
|
||||||
|
19
pkg/payload/payload.go
Normal file
19
pkg/payload/payload.go
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package payload
|
||||||
|
|
||||||
|
type Data struct {
|
||||||
|
Passphrase string `json:"passphrase"`
|
||||||
|
Error string `json:"error"`
|
||||||
|
GeneratedBy string `json:"generated_by"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d Data) HasError() bool {
|
||||||
|
return d.Error != ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d Data) HasPassphrase() bool {
|
||||||
|
return d.Passphrase != ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d Data) HasBeenGenerated() bool {
|
||||||
|
return d.GeneratedBy != ""
|
||||||
|
}
|
Reference in New Issue
Block a user