Initial project snapshot
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
package state
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
)
|
||||
|
||||
const FileName = "identity.json"
|
||||
|
||||
type Identity struct {
|
||||
NodeID string `json:"node_id"`
|
||||
ClusterID string `json:"cluster_id"`
|
||||
NodeName string `json:"node_name"`
|
||||
NodeFingerprint string `json:"node_fingerprint"`
|
||||
PublicKey string `json:"public_key"`
|
||||
IdentityStatus string `json:"identity_status"`
|
||||
PendingJoinRequestID string `json:"pending_join_request_id,omitempty"`
|
||||
ClusterAuthorityPublicKey string `json:"cluster_authority_public_key,omitempty"`
|
||||
ClusterAuthorityFingerprint string `json:"cluster_authority_fingerprint,omitempty"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
func LoadOrCreate(dir, clusterID, nodeName string) (Identity, error) {
|
||||
path := filepath.Join(dir, FileName)
|
||||
existing, err := Load(path)
|
||||
if err == nil {
|
||||
return existing, nil
|
||||
}
|
||||
if !errors.Is(err, os.ErrNotExist) {
|
||||
return Identity{}, err
|
||||
}
|
||||
now := time.Now().UTC()
|
||||
fingerprint, err := randomToken("rap-node-fp")
|
||||
if err != nil {
|
||||
return Identity{}, err
|
||||
}
|
||||
publicKey, err := randomToken("rap-node-pub")
|
||||
if err != nil {
|
||||
return Identity{}, err
|
||||
}
|
||||
identity := Identity{
|
||||
ClusterID: clusterID,
|
||||
NodeName: nodeName,
|
||||
NodeFingerprint: fingerprint,
|
||||
PublicKey: publicKey,
|
||||
IdentityStatus: "new",
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
if err := Save(path, identity); err != nil {
|
||||
return Identity{}, err
|
||||
}
|
||||
return identity, nil
|
||||
}
|
||||
|
||||
func Load(path string) (Identity, error) {
|
||||
payload, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return Identity{}, err
|
||||
}
|
||||
var identity Identity
|
||||
if err := json.Unmarshal(payload, &identity); err != nil {
|
||||
return Identity{}, err
|
||||
}
|
||||
return identity, nil
|
||||
}
|
||||
|
||||
func Save(path string, identity Identity) error {
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil {
|
||||
return err
|
||||
}
|
||||
identity.UpdatedAt = time.Now().UTC()
|
||||
payload, err := json.MarshalIndent(identity, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.WriteFile(path, payload, 0o600)
|
||||
}
|
||||
|
||||
func MarkEnrollmentSubmitted(dir, clusterID, joinRequestID string) (Identity, error) {
|
||||
path := filepath.Join(dir, FileName)
|
||||
identity, err := Load(path)
|
||||
if err != nil {
|
||||
return Identity{}, err
|
||||
}
|
||||
identity.ClusterID = clusterID
|
||||
identity.PendingJoinRequestID = joinRequestID
|
||||
identity.IdentityStatus = "pending_approval"
|
||||
if err := Save(path, identity); err != nil {
|
||||
return Identity{}, err
|
||||
}
|
||||
return identity, nil
|
||||
}
|
||||
|
||||
func MarkApproved(dir string, nodeID, clusterID, status string) (Identity, error) {
|
||||
return MarkApprovedWithAuthority(dir, nodeID, clusterID, status, "", "")
|
||||
}
|
||||
|
||||
func MarkApprovedWithAuthority(dir string, nodeID, clusterID, status, authorityPublicKey, authorityFingerprint string) (Identity, error) {
|
||||
path := filepath.Join(dir, FileName)
|
||||
identity, err := Load(path)
|
||||
if err != nil {
|
||||
return Identity{}, err
|
||||
}
|
||||
identity.NodeID = nodeID
|
||||
identity.ClusterID = clusterID
|
||||
identity.IdentityStatus = status
|
||||
identity.PendingJoinRequestID = ""
|
||||
identity.ClusterAuthorityPublicKey = authorityPublicKey
|
||||
identity.ClusterAuthorityFingerprint = authorityFingerprint
|
||||
if err := Save(path, identity); err != nil {
|
||||
return Identity{}, err
|
||||
}
|
||||
return identity, nil
|
||||
}
|
||||
|
||||
func randomToken(prefix string) (string, error) {
|
||||
var random [32]byte
|
||||
if _, err := rand.Read(random[:]); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return prefix + "_" + base64.RawURLEncoding.EncodeToString(random[:]), nil
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package state
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLoadOrCreatePersistsIdentity(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
identity, err := LoadOrCreate(dir, "cluster-1", "node-a")
|
||||
if err != nil {
|
||||
t.Fatalf("load or create: %v", err)
|
||||
}
|
||||
if identity.NodeFingerprint == "" || identity.PublicKey == "" {
|
||||
t.Fatalf("identity missing generated fields: %+v", identity)
|
||||
}
|
||||
loaded, err := Load(filepath.Join(dir, FileName))
|
||||
if err != nil {
|
||||
t.Fatalf("load identity: %v", err)
|
||||
}
|
||||
if loaded.NodeFingerprint != identity.NodeFingerprint {
|
||||
t.Fatal("identity fingerprint was not persisted")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarkApprovedUpdatesIdentity(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
if _, err := LoadOrCreate(dir, "cluster-1", "node-a"); err != nil {
|
||||
t.Fatalf("load or create: %v", err)
|
||||
}
|
||||
if _, err := MarkEnrollmentSubmitted(dir, "cluster-1", "join-request-1"); err != nil {
|
||||
t.Fatalf("mark enrollment submitted: %v", err)
|
||||
}
|
||||
approved, err := MarkApproved(dir, "node-1", "cluster-1", "active")
|
||||
if err != nil {
|
||||
t.Fatalf("mark approved: %v", err)
|
||||
}
|
||||
if approved.NodeID != "node-1" || approved.IdentityStatus != "active" || approved.PendingJoinRequestID != "" {
|
||||
t.Fatalf("unexpected approved identity: %+v", approved)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarkApprovedWithAuthorityPinsClusterAuthority(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
if _, err := LoadOrCreate(dir, "cluster-1", "node-a"); err != nil {
|
||||
t.Fatalf("load or create: %v", err)
|
||||
}
|
||||
approved, err := MarkApprovedWithAuthority(dir, "node-1", "cluster-1", "active", "public-key-b64", "rap-ca-ed25519-test")
|
||||
if err != nil {
|
||||
t.Fatalf("mark approved with authority: %v", err)
|
||||
}
|
||||
if approved.ClusterAuthorityPublicKey != "public-key-b64" || approved.ClusterAuthorityFingerprint != "rap-ca-ed25519-test" {
|
||||
t.Fatalf("authority pin was not persisted: %+v", approved)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user