package main import ( "context" "regexp" "sync" "github.com/hashicorp/hcl/v2/hclsimple" configv1 "github.com/spiffe/spire-plugin-sdk/proto/spire/service/common/config/v1" workloadattestorv1 "github.com/spiffe/spire-plugin-sdk/proto/spire/plugin/agent/workloadattestor/v1" "github.com/spiffe/spire-plugin-sdk/pluginmain" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) type Plugin struct { workloadattestorv1.UnimplementedWorkloadAttestorServer configv1.UnimplementedConfigServer mu sync.RWMutex attestor *GsapAttestor } func (p *Plugin) Attest(ctx context.Context, req *workloadattestorv1.AttestRequest) (*workloadattestorv1.AttestResponse, error) { p.mu.RLock() a := p.attestor p.mu.RUnlock() if a == nil { return nil, status.Error(codes.FailedPrecondition, "plugin not configured") } selectors, err := a.Attest(ctx, req.Pid) if err != nil { return nil, status.Errorf(codes.Internal, "attestation failed: %v", err) } return &workloadattestorv1.AttestResponse{ SelectorValues: selectors, }, nil } // unquoteHCLKeys strips quotes from HCL v1-style attribute names // (e.g. `"max_depth" = 10` → `max_depth = 10`). SPIRE converts JSON // plugin_data to HCL v1 native syntax which uses quoted keys that // HCL v2's parser rejects. var hclQuotedKey = regexp.MustCompile(`(?m)^(\s*)"([a-zA-Z_][a-zA-Z0-9_]*)"(\s*=)`) func decodeConfig(data string) (GsapAttestorConfig, error) { var cfg GsapAttestorConfig normalized := hclQuotedKey.ReplaceAllString(data, `${1}${2}${3}`) if err := hclsimple.Decode("plugin.hcl", []byte(normalized), nil, &cfg); err != nil { return cfg, err } return cfg, nil } func (p *Plugin) Configure(_ context.Context, req *configv1.ConfigureRequest) (*configv1.ConfigureResponse, error) { cfg, err := decodeConfig(req.HclConfiguration) if err != nil { return nil, status.Errorf(codes.InvalidArgument, "failed to decode configuration: %v", err) } a := &GsapAttestor{} if err := a.Configure(cfg); err != nil { return nil, status.Errorf(codes.InvalidArgument, "invalid configuration: %v", err) } p.mu.Lock() p.attestor = a p.mu.Unlock() return &configv1.ConfigureResponse{}, nil } func (p *Plugin) Validate(_ context.Context, req *configv1.ValidateRequest) (*configv1.ValidateResponse, error) { if _, err := decodeConfig(req.HclConfiguration); err != nil { return nil, status.Errorf(codes.InvalidArgument, "failed to decode configuration: %v", err) } return &configv1.ValidateResponse{}, nil } func main() { p := &Plugin{} pluginmain.Serve( workloadattestorv1.WorkloadAttestorPluginServer(p), configv1.ConfigServiceServer(p), ) }