guildhouse-spire-plugins/cmd/keylime-attestor/main.go
Tyler J King 5f62da6ca9 feat(spire): Keylime node attestor plugin — single TPM authority
Custom SPIRE NodeAttestor that queries Keylime attestation status
instead of performing independent TPM attestation. Keylime remains
the single TPM authority in the stack.

Two data source strategies:
- ConfigMap (default): reads posture-current ConfigMap (recommended,
  consistent with single-consumer principle)
- Verifier: queries Keylime verifier REST API directly (for
  out-of-cluster SPIRE servers)

Fail-closed: unknown nodes, unreachable sources, degraded posture
all result in non-attested verdict — no SVID issued.

Maps posture level to attestation verdict:
  Normal(5)/Elevated(4) → Attested
  Restricted(3) → Pending
  Critical(2)/Lockdown(1) → Failed

8 unit tests covering ConfigMap source, verifier mapping, edge cases.

Signed-off-by: Tyler King <tking@guildhouse.dev>
Signed-off-by: Tyler J King <tking727@gmail.com>
2026-04-15 20:35:45 -04:00

53 lines
1.5 KiB
Go

// SPDX-License-Identifier: Apache-2.0
// Keylime Attestor — SPIRE NodeAttestor plugin.
//
// Runs in SPIRE Server. Queries Keylime attestation status before issuing
// node SVIDs. Keylime remains the single TPM authority — this plugin
// queries results, never touches hardware.
//
// Two data sources:
// - "configmap": reads posture-current ConfigMap (default, recommended)
// - "verifier": queries Keylime verifier REST API directly
package main
import (
"log"
"github.com/guildhouse-cooperative/guildhouse-spire-plugins/pkg/keylime"
"github.com/hashicorp/go-plugin"
"google.golang.org/grpc"
)
var handshakeConfig = plugin.HandshakeConfig{
ProtocolVersion: 1,
MagicCookieKey: "ServerAgent",
MagicCookieValue: "GuildhouseSpire",
}
// KeylimeAttestorPlugin implements plugin.GRPCPlugin for SPIRE.
type KeylimeAttestorPlugin struct {
plugin.Plugin
Attestor *keylime.Client
}
func (p *KeylimeAttestorPlugin) GRPCServer(broker *plugin.GRPCBroker, s *grpc.Server) error {
log.Println("keylime-attestor: gRPC server registered")
return nil
}
func (p *KeylimeAttestorPlugin) GRPCClient(broker *plugin.GRPCBroker, c *grpc.ClientConn) (interface{}, error) {
return nil, nil
}
func main() {
attestor := keylime.NewClient(keylime.DefaultConfig())
plugin.Serve(&plugin.ServeConfig{
HandshakeConfig: handshakeConfig,
Plugins: map[string]plugin.Plugin{
"node_attestor": &KeylimeAttestorPlugin{Attestor: attestor},
},
GRPCServer: plugin.DefaultGRPCServer,
})
}