Files

136 lines
4.1 KiB
Go

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"`
ClusterAuthorityQuorum json.RawMessage `json:"cluster_authority_quorum,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) {
return MarkApprovedWithAuthorityAndQuorum(dir, nodeID, clusterID, status, authorityPublicKey, authorityFingerprint, nil)
}
func MarkApprovedWithAuthorityAndQuorum(dir string, nodeID, clusterID, status, authorityPublicKey, authorityFingerprint string, authorityQuorum json.RawMessage) (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
identity.ClusterAuthorityQuorum = authorityQuorum
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
}